grafeo-ogm 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/LICENSE +21 -0
- package/README.md +1121 -0
- package/dist/cjs/compilers/fulltext.compiler.d.ts +67 -0
- package/dist/cjs/compilers/fulltext.compiler.d.ts.map +1 -0
- package/dist/cjs/compilers/fulltext.compiler.js +254 -0
- package/dist/cjs/compilers/fulltext.compiler.js.map +1 -0
- package/dist/cjs/compilers/mutation.compiler.d.ts +126 -0
- package/dist/cjs/compilers/mutation.compiler.d.ts.map +1 -0
- package/dist/cjs/compilers/mutation.compiler.js +1239 -0
- package/dist/cjs/compilers/mutation.compiler.js.map +1 -0
- package/dist/cjs/compilers/select-normalizer.d.ts +50 -0
- package/dist/cjs/compilers/select-normalizer.d.ts.map +1 -0
- package/dist/cjs/compilers/select-normalizer.js +225 -0
- package/dist/cjs/compilers/select-normalizer.js.map +1 -0
- package/dist/cjs/compilers/selection.compiler.d.ts +113 -0
- package/dist/cjs/compilers/selection.compiler.d.ts.map +1 -0
- package/dist/cjs/compilers/selection.compiler.js +410 -0
- package/dist/cjs/compilers/selection.compiler.js.map +1 -0
- package/dist/cjs/compilers/where.compiler.d.ts +39 -0
- package/dist/cjs/compilers/where.compiler.d.ts.map +1 -0
- package/dist/cjs/compilers/where.compiler.js +456 -0
- package/dist/cjs/compilers/where.compiler.js.map +1 -0
- package/dist/cjs/errors.d.ts +28 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +42 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/execution/executor.d.ts +38 -0
- package/dist/cjs/execution/executor.d.ts.map +1 -0
- package/dist/cjs/execution/executor.js +50 -0
- package/dist/cjs/execution/executor.js.map +1 -0
- package/dist/cjs/execution/result-mapper.d.ts +26 -0
- package/dist/cjs/execution/result-mapper.d.ts.map +1 -0
- package/dist/cjs/execution/result-mapper.js +92 -0
- package/dist/cjs/execution/result-mapper.js.map +1 -0
- package/dist/cjs/generator/generate-types.d.ts +55 -0
- package/dist/cjs/generator/generate-types.d.ts.map +1 -0
- package/dist/cjs/generator/generate-types.js +181 -0
- package/dist/cjs/generator/generate-types.js.map +1 -0
- package/dist/cjs/generator/index.d.ts +3 -0
- package/dist/cjs/generator/index.d.ts.map +1 -0
- package/dist/cjs/generator/index.js +6 -0
- package/dist/cjs/generator/index.js.map +1 -0
- package/dist/cjs/generator/type-emitters/aggregation-emitter.d.ts +7 -0
- package/dist/cjs/generator/type-emitters/aggregation-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/aggregation-emitter.js +196 -0
- package/dist/cjs/generator/type-emitters/aggregation-emitter.js.map +1 -0
- package/dist/cjs/generator/type-emitters/connection-emitter.d.ts +22 -0
- package/dist/cjs/generator/type-emitters/connection-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/connection-emitter.js +270 -0
- package/dist/cjs/generator/type-emitters/connection-emitter.js.map +1 -0
- package/dist/cjs/generator/type-emitters/enum-emitter.d.ts +7 -0
- package/dist/cjs/generator/type-emitters/enum-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/enum-emitter.js +45 -0
- package/dist/cjs/generator/type-emitters/enum-emitter.js.map +1 -0
- package/dist/cjs/generator/type-emitters/fulltext-emitter.d.ts +15 -0
- package/dist/cjs/generator/type-emitters/fulltext-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/fulltext-emitter.js +80 -0
- package/dist/cjs/generator/type-emitters/fulltext-emitter.js.map +1 -0
- package/dist/cjs/generator/type-emitters/helpers.d.ts +32 -0
- package/dist/cjs/generator/type-emitters/helpers.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/helpers.js +69 -0
- package/dist/cjs/generator/type-emitters/helpers.js.map +1 -0
- package/dist/cjs/generator/type-emitters/input-emitter.d.ts +6 -0
- package/dist/cjs/generator/type-emitters/input-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/input-emitter.js +560 -0
- package/dist/cjs/generator/type-emitters/input-emitter.js.map +1 -0
- package/dist/cjs/generator/type-emitters/interface-emitter.d.ts +10 -0
- package/dist/cjs/generator/type-emitters/interface-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/interface-emitter.js +88 -0
- package/dist/cjs/generator/type-emitters/interface-emitter.js.map +1 -0
- package/dist/cjs/generator/type-emitters/model-map-emitter.d.ts +32 -0
- package/dist/cjs/generator/type-emitters/model-map-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/model-map-emitter.js +154 -0
- package/dist/cjs/generator/type-emitters/model-map-emitter.js.map +1 -0
- package/dist/cjs/generator/type-emitters/mutation-response-emitter.d.ts +9 -0
- package/dist/cjs/generator/type-emitters/mutation-response-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/mutation-response-emitter.js +69 -0
- package/dist/cjs/generator/type-emitters/mutation-response-emitter.js.map +1 -0
- package/dist/cjs/generator/type-emitters/node-type-emitter.d.ts +13 -0
- package/dist/cjs/generator/type-emitters/node-type-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/node-type-emitter.js +123 -0
- package/dist/cjs/generator/type-emitters/node-type-emitter.js.map +1 -0
- package/dist/cjs/generator/type-emitters/select-fields-emitter.d.ts +8 -0
- package/dist/cjs/generator/type-emitters/select-fields-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/select-fields-emitter.js +169 -0
- package/dist/cjs/generator/type-emitters/select-fields-emitter.js.map +1 -0
- package/dist/cjs/generator/type-emitters/select-result-emitter.d.ts +9 -0
- package/dist/cjs/generator/type-emitters/select-result-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/select-result-emitter.js +61 -0
- package/dist/cjs/generator/type-emitters/select-result-emitter.js.map +1 -0
- package/dist/cjs/generator/type-emitters/sort-options-emitter.d.ts +8 -0
- package/dist/cjs/generator/type-emitters/sort-options-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/sort-options-emitter.js +47 -0
- package/dist/cjs/generator/type-emitters/sort-options-emitter.js.map +1 -0
- package/dist/cjs/generator/type-emitters/utility-types.d.ts +10 -0
- package/dist/cjs/generator/type-emitters/utility-types.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/utility-types.js +93 -0
- package/dist/cjs/generator/type-emitters/utility-types.js.map +1 -0
- package/dist/cjs/generator/type-emitters/where-emitter.d.ts +15 -0
- package/dist/cjs/generator/type-emitters/where-emitter.d.ts.map +1 -0
- package/dist/cjs/generator/type-emitters/where-emitter.js +189 -0
- package/dist/cjs/generator/type-emitters/where-emitter.js.map +1 -0
- package/dist/cjs/index.d.ts +30 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +59 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/interface-model.d.ts +158 -0
- package/dist/cjs/interface-model.d.ts.map +1 -0
- package/dist/cjs/interface-model.js +214 -0
- package/dist/cjs/interface-model.js.map +1 -0
- package/dist/cjs/model.d.ts +354 -0
- package/dist/cjs/model.d.ts.map +1 -0
- package/dist/cjs/model.js +513 -0
- package/dist/cjs/model.js.map +1 -0
- package/dist/cjs/ogm.d.ts +97 -0
- package/dist/cjs/ogm.d.ts.map +1 -0
- package/dist/cjs/ogm.js +224 -0
- package/dist/cjs/ogm.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/schema/parser.d.ts +12 -0
- package/dist/cjs/schema/parser.d.ts.map +1 -0
- package/dist/cjs/schema/parser.js +331 -0
- package/dist/cjs/schema/parser.js.map +1 -0
- package/dist/cjs/schema/types.d.ts +66 -0
- package/dist/cjs/schema/types.d.ts.map +1 -0
- package/dist/cjs/schema/types.js +3 -0
- package/dist/cjs/schema/types.js.map +1 -0
- package/dist/cjs/schema/utils.d.ts +23 -0
- package/dist/cjs/schema/utils.d.ts.map +1 -0
- package/dist/cjs/schema/utils.js +92 -0
- package/dist/cjs/schema/utils.js.map +1 -0
- package/dist/cjs/subgraph/index.d.ts +3 -0
- package/dist/cjs/subgraph/index.d.ts.map +1 -0
- package/dist/cjs/subgraph/index.js +7 -0
- package/dist/cjs/subgraph/index.js.map +1 -0
- package/dist/cjs/subgraph/subgraph-operations.d.ts +34 -0
- package/dist/cjs/subgraph/subgraph-operations.d.ts.map +1 -0
- package/dist/cjs/subgraph/subgraph-operations.js +225 -0
- package/dist/cjs/subgraph/subgraph-operations.js.map +1 -0
- package/dist/cjs/subgraph/types.d.ts +23 -0
- package/dist/cjs/subgraph/types.d.ts.map +1 -0
- package/dist/cjs/subgraph/types.js +3 -0
- package/dist/cjs/subgraph/types.js.map +1 -0
- package/dist/cjs/testing/cypher-assert.d.ts +35 -0
- package/dist/cjs/testing/cypher-assert.d.ts.map +1 -0
- package/dist/cjs/testing/cypher-assert.js +72 -0
- package/dist/cjs/testing/cypher-assert.js.map +1 -0
- package/dist/cjs/testing/index.d.ts +4 -0
- package/dist/cjs/testing/index.d.ts.map +1 -0
- package/dist/cjs/testing/index.js +10 -0
- package/dist/cjs/testing/index.js.map +1 -0
- package/dist/cjs/testing/neo4j-record-factory.d.ts +25 -0
- package/dist/cjs/testing/neo4j-record-factory.d.ts.map +1 -0
- package/dist/cjs/testing/neo4j-record-factory.js +43 -0
- package/dist/cjs/testing/neo4j-record-factory.js.map +1 -0
- package/dist/cjs/testing/selection-set-factory.d.ts +24 -0
- package/dist/cjs/testing/selection-set-factory.d.ts.map +1 -0
- package/dist/cjs/testing/selection-set-factory.js +44 -0
- package/dist/cjs/testing/selection-set-factory.js.map +1 -0
- package/dist/cjs/utils/lucene.d.ts +9 -0
- package/dist/cjs/utils/lucene.d.ts.map +1 -0
- package/dist/cjs/utils/lucene.js +25 -0
- package/dist/cjs/utils/lucene.js.map +1 -0
- package/dist/cjs/utils/validation.d.ts +25 -0
- package/dist/cjs/utils/validation.d.ts.map +1 -0
- package/dist/cjs/utils/validation.js +53 -0
- package/dist/cjs/utils/validation.js.map +1 -0
- package/dist/esm/compilers/fulltext.compiler.d.ts +67 -0
- package/dist/esm/compilers/fulltext.compiler.d.ts.map +1 -0
- package/dist/esm/compilers/fulltext.compiler.js +250 -0
- package/dist/esm/compilers/fulltext.compiler.js.map +1 -0
- package/dist/esm/compilers/mutation.compiler.d.ts +126 -0
- package/dist/esm/compilers/mutation.compiler.d.ts.map +1 -0
- package/dist/esm/compilers/mutation.compiler.js +1235 -0
- package/dist/esm/compilers/mutation.compiler.js.map +1 -0
- package/dist/esm/compilers/select-normalizer.d.ts +50 -0
- package/dist/esm/compilers/select-normalizer.d.ts.map +1 -0
- package/dist/esm/compilers/select-normalizer.js +221 -0
- package/dist/esm/compilers/select-normalizer.js.map +1 -0
- package/dist/esm/compilers/selection.compiler.d.ts +113 -0
- package/dist/esm/compilers/selection.compiler.d.ts.map +1 -0
- package/dist/esm/compilers/selection.compiler.js +406 -0
- package/dist/esm/compilers/selection.compiler.js.map +1 -0
- package/dist/esm/compilers/where.compiler.d.ts +39 -0
- package/dist/esm/compilers/where.compiler.d.ts.map +1 -0
- package/dist/esm/compilers/where.compiler.js +452 -0
- package/dist/esm/compilers/where.compiler.js.map +1 -0
- package/dist/esm/errors.d.ts +28 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +36 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/execution/executor.d.ts +38 -0
- package/dist/esm/execution/executor.d.ts.map +1 -0
- package/dist/esm/execution/executor.js +46 -0
- package/dist/esm/execution/executor.js.map +1 -0
- package/dist/esm/execution/result-mapper.d.ts +26 -0
- package/dist/esm/execution/result-mapper.d.ts.map +1 -0
- package/dist/esm/execution/result-mapper.js +85 -0
- package/dist/esm/execution/result-mapper.js.map +1 -0
- package/dist/esm/generator/generate-types.d.ts +55 -0
- package/dist/esm/generator/generate-types.d.ts.map +1 -0
- package/dist/esm/generator/generate-types.js +141 -0
- package/dist/esm/generator/generate-types.js.map +1 -0
- package/dist/esm/generator/index.d.ts +3 -0
- package/dist/esm/generator/index.d.ts.map +1 -0
- package/dist/esm/generator/index.js +2 -0
- package/dist/esm/generator/index.js.map +1 -0
- package/dist/esm/generator/type-emitters/aggregation-emitter.d.ts +7 -0
- package/dist/esm/generator/type-emitters/aggregation-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/aggregation-emitter.js +193 -0
- package/dist/esm/generator/type-emitters/aggregation-emitter.js.map +1 -0
- package/dist/esm/generator/type-emitters/connection-emitter.d.ts +22 -0
- package/dist/esm/generator/type-emitters/connection-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/connection-emitter.js +266 -0
- package/dist/esm/generator/type-emitters/connection-emitter.js.map +1 -0
- package/dist/esm/generator/type-emitters/enum-emitter.d.ts +7 -0
- package/dist/esm/generator/type-emitters/enum-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/enum-emitter.js +42 -0
- package/dist/esm/generator/type-emitters/enum-emitter.js.map +1 -0
- package/dist/esm/generator/type-emitters/fulltext-emitter.d.ts +15 -0
- package/dist/esm/generator/type-emitters/fulltext-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/fulltext-emitter.js +77 -0
- package/dist/esm/generator/type-emitters/fulltext-emitter.js.map +1 -0
- package/dist/esm/generator/type-emitters/helpers.d.ts +32 -0
- package/dist/esm/generator/type-emitters/helpers.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/helpers.js +63 -0
- package/dist/esm/generator/type-emitters/helpers.js.map +1 -0
- package/dist/esm/generator/type-emitters/input-emitter.d.ts +6 -0
- package/dist/esm/generator/type-emitters/input-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/input-emitter.js +557 -0
- package/dist/esm/generator/type-emitters/input-emitter.js.map +1 -0
- package/dist/esm/generator/type-emitters/interface-emitter.d.ts +10 -0
- package/dist/esm/generator/type-emitters/interface-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/interface-emitter.js +85 -0
- package/dist/esm/generator/type-emitters/interface-emitter.js.map +1 -0
- package/dist/esm/generator/type-emitters/model-map-emitter.d.ts +32 -0
- package/dist/esm/generator/type-emitters/model-map-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/model-map-emitter.js +149 -0
- package/dist/esm/generator/type-emitters/model-map-emitter.js.map +1 -0
- package/dist/esm/generator/type-emitters/mutation-response-emitter.d.ts +9 -0
- package/dist/esm/generator/type-emitters/mutation-response-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/mutation-response-emitter.js +66 -0
- package/dist/esm/generator/type-emitters/mutation-response-emitter.js.map +1 -0
- package/dist/esm/generator/type-emitters/node-type-emitter.d.ts +13 -0
- package/dist/esm/generator/type-emitters/node-type-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/node-type-emitter.js +120 -0
- package/dist/esm/generator/type-emitters/node-type-emitter.js.map +1 -0
- package/dist/esm/generator/type-emitters/select-fields-emitter.d.ts +8 -0
- package/dist/esm/generator/type-emitters/select-fields-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/select-fields-emitter.js +166 -0
- package/dist/esm/generator/type-emitters/select-fields-emitter.js.map +1 -0
- package/dist/esm/generator/type-emitters/select-result-emitter.d.ts +9 -0
- package/dist/esm/generator/type-emitters/select-result-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/select-result-emitter.js +58 -0
- package/dist/esm/generator/type-emitters/select-result-emitter.js.map +1 -0
- package/dist/esm/generator/type-emitters/sort-options-emitter.d.ts +8 -0
- package/dist/esm/generator/type-emitters/sort-options-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/sort-options-emitter.js +44 -0
- package/dist/esm/generator/type-emitters/sort-options-emitter.js.map +1 -0
- package/dist/esm/generator/type-emitters/utility-types.d.ts +10 -0
- package/dist/esm/generator/type-emitters/utility-types.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/utility-types.js +88 -0
- package/dist/esm/generator/type-emitters/utility-types.js.map +1 -0
- package/dist/esm/generator/type-emitters/where-emitter.d.ts +15 -0
- package/dist/esm/generator/type-emitters/where-emitter.d.ts.map +1 -0
- package/dist/esm/generator/type-emitters/where-emitter.js +186 -0
- package/dist/esm/generator/type-emitters/where-emitter.js.map +1 -0
- package/dist/esm/index.d.ts +30 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +29 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/interface-model.d.ts +158 -0
- package/dist/esm/interface-model.d.ts.map +1 -0
- package/dist/esm/interface-model.js +210 -0
- package/dist/esm/interface-model.js.map +1 -0
- package/dist/esm/model.d.ts +354 -0
- package/dist/esm/model.d.ts.map +1 -0
- package/dist/esm/model.js +507 -0
- package/dist/esm/model.js.map +1 -0
- package/dist/esm/ogm.d.ts +97 -0
- package/dist/esm/ogm.d.ts.map +1 -0
- package/dist/esm/ogm.js +220 -0
- package/dist/esm/ogm.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/schema/parser.d.ts +12 -0
- package/dist/esm/schema/parser.d.ts.map +1 -0
- package/dist/esm/schema/parser.js +324 -0
- package/dist/esm/schema/parser.js.map +1 -0
- package/dist/esm/schema/types.d.ts +66 -0
- package/dist/esm/schema/types.d.ts.map +1 -0
- package/dist/esm/schema/types.js +2 -0
- package/dist/esm/schema/types.js.map +1 -0
- package/dist/esm/schema/utils.d.ts +23 -0
- package/dist/esm/schema/utils.d.ts.map +1 -0
- package/dist/esm/schema/utils.js +87 -0
- package/dist/esm/schema/utils.js.map +1 -0
- package/dist/esm/subgraph/index.d.ts +3 -0
- package/dist/esm/subgraph/index.d.ts.map +1 -0
- package/dist/esm/subgraph/index.js +2 -0
- package/dist/esm/subgraph/index.js.map +1 -0
- package/dist/esm/subgraph/subgraph-operations.d.ts +34 -0
- package/dist/esm/subgraph/subgraph-operations.d.ts.map +1 -0
- package/dist/esm/subgraph/subgraph-operations.js +221 -0
- package/dist/esm/subgraph/subgraph-operations.js.map +1 -0
- package/dist/esm/subgraph/types.d.ts +23 -0
- package/dist/esm/subgraph/types.d.ts.map +1 -0
- package/dist/esm/subgraph/types.js +2 -0
- package/dist/esm/subgraph/types.js.map +1 -0
- package/dist/esm/testing/cypher-assert.d.ts +35 -0
- package/dist/esm/testing/cypher-assert.d.ts.map +1 -0
- package/dist/esm/testing/cypher-assert.js +68 -0
- package/dist/esm/testing/cypher-assert.js.map +1 -0
- package/dist/esm/testing/index.d.ts +4 -0
- package/dist/esm/testing/index.d.ts.map +1 -0
- package/dist/esm/testing/index.js +4 -0
- package/dist/esm/testing/index.js.map +1 -0
- package/dist/esm/testing/neo4j-record-factory.d.ts +25 -0
- package/dist/esm/testing/neo4j-record-factory.d.ts.map +1 -0
- package/dist/esm/testing/neo4j-record-factory.js +36 -0
- package/dist/esm/testing/neo4j-record-factory.js.map +1 -0
- package/dist/esm/testing/selection-set-factory.d.ts +24 -0
- package/dist/esm/testing/selection-set-factory.d.ts.map +1 -0
- package/dist/esm/testing/selection-set-factory.js +40 -0
- package/dist/esm/testing/selection-set-factory.js.map +1 -0
- package/dist/esm/utils/lucene.d.ts +9 -0
- package/dist/esm/utils/lucene.d.ts.map +1 -0
- package/dist/esm/utils/lucene.js +22 -0
- package/dist/esm/utils/lucene.js.map +1 -0
- package/dist/esm/utils/validation.d.ts +25 -0
- package/dist/esm/utils/validation.d.ts.map +1 -0
- package/dist/esm/utils/validation.js +46 -0
- package/dist/esm/utils/validation.js.map +1 -0
- package/package.json +88 -0
package/README.md
ADDED
|
@@ -0,0 +1,1121 @@
|
|
|
1
|
+
# grafeo-ogm
|
|
2
|
+
|
|
3
|
+
**A type-safe Object-Graph Mapper for Neo4j, driven by GraphQL SDL.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/grafeo-ogm)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://nodejs.org/)
|
|
9
|
+
[](https://neo4j.com/)
|
|
10
|
+
|
|
11
|
+
Define your graph model once in `.graphql` and get fully typed CRUD operations, optimized Cypher compilation, and TypeScript code generation -- no boilerplate, no Cartesian products, no injection vulnerabilities.
|
|
12
|
+
|
|
13
|
+
> **grafeo** (*/ˈɡra.fe.o/*) -- from Greek *grapho* (write, draw), the root behind "graph." Because your data model should be written once and understood everywhere.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Table of Contents
|
|
18
|
+
|
|
19
|
+
- [Why grafeo-ogm?](#why-grafeo-ogm)
|
|
20
|
+
- [Quick Start](#quick-start)
|
|
21
|
+
- [Features Overview](#features-overview)
|
|
22
|
+
- [Schema Definition](#schema-definition)
|
|
23
|
+
- [Query API](#query-api)
|
|
24
|
+
- [Mutation API](#mutation-api)
|
|
25
|
+
- [Advanced Features](#advanced-features)
|
|
26
|
+
- [Type Generation](#type-generation)
|
|
27
|
+
- [Testing Utilities](#testing-utilities)
|
|
28
|
+
- [Security](#security)
|
|
29
|
+
- [Migration from @neo4j/graphql-ogm](#migration-from-neo4jgraphql-ogm)
|
|
30
|
+
- [API Reference](#api-reference)
|
|
31
|
+
- [Contributing](#contributing)
|
|
32
|
+
- [License](#license)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Why grafeo-ogm?
|
|
37
|
+
|
|
38
|
+
The official [`@neo4j/graphql-ogm`](https://github.com/neo4j/graphql/tree/changeset-release/lts/packages/ogm/) was deprecated with no development path beyond v5. grafeo-ogm is a community continuation that maintains **full backward compatibility** while addressing five fundamental problems the original library could never patch:
|
|
39
|
+
|
|
40
|
+
| Problem | @neo4j/graphql-ogm | grafeo-ogm |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| **Deprecation** | Abandoned by Neo4j; no path beyond v5 | Actively maintained; evolves with your schema |
|
|
43
|
+
| **Query efficiency** | `OPTIONAL MATCH` chains that create Cartesian products across relationships | Pattern comprehensions scoped to matched nodes -- O(1) rows per node regardless of relationship count |
|
|
44
|
+
| **Security** | No identifier validation, no injection prevention, no parameterization enforcement | 7 security controls: parameterization, identifier regex, label validation, prototype pollution prevention, Lucene escaping, depth limiting, sort direction validation |
|
|
45
|
+
| **Type generation** | Monolithic multi-MB output; required a running Neo4j driver at build time | Modular emitter pipeline; no driver needed at build time; output sized proportionally to schema |
|
|
46
|
+
| **Customization** | No union type support, no subquery isolation, no edge property queries | Full union/interface support, `CALL` subquery isolation for mutations, connection fields with edge properties |
|
|
47
|
+
|
|
48
|
+
### Pattern Comprehensions vs. OPTIONAL MATCH
|
|
49
|
+
|
|
50
|
+
The most impactful difference is how relationships are queried. The old OGM chains `OPTIONAL MATCH` clauses that multiply intermediate rows:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
10 variants x 5 categories x 3 tags = 150 intermediate rows per node
|
|
54
|
+
Query 200 nodes = 30,000 rows processed
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
grafeo-ogm uses pattern comprehensions that evaluate each relationship independently inside the RETURN projection:
|
|
58
|
+
|
|
59
|
+
```cypher
|
|
60
|
+
RETURN n {
|
|
61
|
+
.id, .name,
|
|
62
|
+
variants: [(n)-[:HAS_VARIANT]->(n0:Variant) | n0 { .id, .value }],
|
|
63
|
+
status: head([(n)-[:HAS_STATUS]->(n0:Status) | n0 { .id, .name }])
|
|
64
|
+
}
|
|
65
|
+
-- Always 1 row per node. Adding relationships adds list expressions, never rows.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Query | Old OGM (rows) | grafeo-ogm (rows) | Reduction |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| 1 node, 10+5+3 rels | 150 | 1 | 150x |
|
|
71
|
+
| 50 nodes, same rels | 7,500 | 50 | 150x |
|
|
72
|
+
| 200 nodes, same rels | 30,000 | 200 | 150x |
|
|
73
|
+
|
|
74
|
+
### CALL Subquery Isolation
|
|
75
|
+
|
|
76
|
+
Mutations with multiple connect/disconnect operations are wrapped in `CALL { ... }` subqueries. Each operation runs in isolation -- no Cartesian products, no duplicate relationships, no silently dropped operations when a disconnect target doesn't exist.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Quick Start
|
|
81
|
+
|
|
82
|
+
### Installation
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# npm
|
|
86
|
+
npm install grafeo-ogm neo4j-driver graphql
|
|
87
|
+
|
|
88
|
+
# pnpm
|
|
89
|
+
pnpm add grafeo-ogm neo4j-driver graphql
|
|
90
|
+
|
|
91
|
+
# yarn
|
|
92
|
+
yarn add grafeo-ogm neo4j-driver graphql
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 1. Define your schema
|
|
96
|
+
|
|
97
|
+
```graphql
|
|
98
|
+
# schema.graphql
|
|
99
|
+
type Book @node
|
|
100
|
+
@fulltext(indexes: [{ name: "BookSearch", fields: ["title"] }]) {
|
|
101
|
+
id: ID! @id @unique
|
|
102
|
+
title: String!
|
|
103
|
+
isbn: String @unique
|
|
104
|
+
published: DateTime
|
|
105
|
+
author: Author! @relationship(type: "WRITTEN_BY", direction: OUT)
|
|
106
|
+
categories: [Category!]! @relationship(type: "IN_CATEGORY", direction: OUT)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type Author @node {
|
|
110
|
+
id: ID! @id @unique
|
|
111
|
+
name: String!
|
|
112
|
+
books: [Book!]! @relationship(type: "WRITTEN_BY", direction: IN)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
type Category @node {
|
|
116
|
+
id: ID! @id @unique
|
|
117
|
+
name: String!
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 2. Initialize the OGM
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { OGM } from 'grafeo-ogm';
|
|
125
|
+
import neo4j from 'neo4j-driver';
|
|
126
|
+
import fs from 'fs';
|
|
127
|
+
|
|
128
|
+
const driver = neo4j.driver(
|
|
129
|
+
'bolt://localhost:7687',
|
|
130
|
+
neo4j.auth.basic('neo4j', 'password'),
|
|
131
|
+
);
|
|
132
|
+
const typeDefs = fs.readFileSync('./schema.graphql', 'utf-8');
|
|
133
|
+
|
|
134
|
+
const ogm = new OGM({ typeDefs, driver });
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 3. Basic CRUD
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const Book = ogm.model('Book');
|
|
141
|
+
|
|
142
|
+
// Create
|
|
143
|
+
const result = await Book.create({
|
|
144
|
+
input: [{
|
|
145
|
+
title: 'Graph Databases',
|
|
146
|
+
isbn: '978-1491930892',
|
|
147
|
+
author: { connect: { where: { node: { name: 'Jim Webber' } } } },
|
|
148
|
+
}],
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Read
|
|
152
|
+
const books = await Book.find({
|
|
153
|
+
where: { title_CONTAINS: 'Graph' },
|
|
154
|
+
select: {
|
|
155
|
+
id: true,
|
|
156
|
+
title: true,
|
|
157
|
+
author: { select: { name: true } },
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Update
|
|
162
|
+
await Book.update({
|
|
163
|
+
where: { isbn: '978-1491930892' },
|
|
164
|
+
update: { title: 'Graph Databases, 2nd Edition' },
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Delete
|
|
168
|
+
await Book.delete({ where: { isbn: '978-1491930892' } });
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 4. Transactions
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
await ogm.$transaction(async (ctx) => {
|
|
175
|
+
await Book.create({ input: [{ title: 'Book A' }] }, { context: ctx });
|
|
176
|
+
await Book.create({ input: [{ title: 'Book B' }] }, { context: ctx });
|
|
177
|
+
// Both committed atomically, or both rolled back on error
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Features Overview
|
|
184
|
+
|
|
185
|
+
| Feature | Description |
|
|
186
|
+
|---|---|
|
|
187
|
+
| **Prisma-like query API** | `find`, `findFirst`, `findUnique`, `findFirstOrThrow`, `findUniqueOrThrow`, `create`, `createMany`, `update`, `updateMany`, `delete`, `deleteMany`, `upsert`, `count`, `aggregate` |
|
|
188
|
+
| **GraphQL SDL schemas** | Define nodes, relationships, interfaces, unions, enums, and fulltext indexes using Neo4j GraphQL directives |
|
|
189
|
+
| **Full TypeScript type safety** | Code generation produces typed models, where inputs, create/update inputs, select fields, and connection types |
|
|
190
|
+
| **Pattern comprehensions** | Relationship traversal without Cartesian products -- O(1) rows per node |
|
|
191
|
+
| **Typed `select` API** | `select: { id: true, author: { select: { name: true } } }` with compile-time type checking |
|
|
192
|
+
| **Fulltext search** | Node and relationship indexes with phrase matching, score thresholds, and logical operators (`AND`, `OR`, `NOT`) |
|
|
193
|
+
| **Subgraph operations** | Clone and delete entire subgraphs via APOC with reference relationship re-attachment |
|
|
194
|
+
| **Runtime multi-label** | Add/remove/filter by labels at query time: `labels: ['Active']`, `setLabels()` |
|
|
195
|
+
| **Interface models** | Polymorphic read queries across all types implementing a shared interface, with `__typename` discrimination |
|
|
196
|
+
| **Connection types** | Cursor pagination with edge properties via `*Connection` fields |
|
|
197
|
+
| **Nested mutations** | Create, connect, disconnect, and cascade-delete related nodes in a single operation |
|
|
198
|
+
| **Transaction support** | `$transaction()` for atomic multi-operation writes with automatic commit/rollback |
|
|
199
|
+
| **Raw Cypher** | `$queryRaw()` and `$executeRaw()` for escape hatches |
|
|
200
|
+
| **7 security controls** | Parameterization, identifier validation, label validation, prototype pollution prevention, Lucene escaping, depth limiting, sort direction validation |
|
|
201
|
+
| **Testing utilities** | `CypherAssert`, `Neo4jRecordFactory`, `SelectionSetFactory` for unit testing without a database |
|
|
202
|
+
| **Backward compatible** | Drop-in replacement for `@neo4j/graphql-ogm` |
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Schema Definition
|
|
207
|
+
|
|
208
|
+
grafeo-ogm uses standard GraphQL SDL with Neo4j directives to define your graph model.
|
|
209
|
+
|
|
210
|
+
### Supported Directives
|
|
211
|
+
|
|
212
|
+
| Directive | Target | Description |
|
|
213
|
+
|---|---|---|
|
|
214
|
+
| `@node` | Type | Marks a type as a Neo4j node. Optional `labels` argument for multi-label nodes |
|
|
215
|
+
| `@relationship` | Field | Defines a relationship with `type`, `direction` (`IN`/`OUT`), and optional `properties` |
|
|
216
|
+
| `@relationshipProperties` | Type | Marks a type as relationship properties (edge data) |
|
|
217
|
+
| `@id` | Field | Auto-generates a UUID on create |
|
|
218
|
+
| `@unique` | Field | Creates a uniqueness constraint in Neo4j |
|
|
219
|
+
| `@cypher` | Field | Computed field resolved via a custom Cypher statement |
|
|
220
|
+
| `@default` | Field | Sets a default value on create |
|
|
221
|
+
| `@fulltext` | Type | Defines fulltext search indexes with `name` and `fields` |
|
|
222
|
+
|
|
223
|
+
### Supported Scalar Types
|
|
224
|
+
|
|
225
|
+
| Type | Neo4j Mapping | Notes |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| `String` | String | |
|
|
228
|
+
| `Int` | Integer | |
|
|
229
|
+
| `Float` | Float | |
|
|
230
|
+
| `Boolean` | Boolean | |
|
|
231
|
+
| `ID` | String | Typically used with `@id` for UUID generation |
|
|
232
|
+
| `DateTime` | DateTime | ISO 8601 format |
|
|
233
|
+
| `Date` | Date | |
|
|
234
|
+
| `Time` | Time | |
|
|
235
|
+
| `LocalTime` | LocalTime | |
|
|
236
|
+
| `LocalDateTime` | LocalDateTime | |
|
|
237
|
+
| `Duration` | Duration | |
|
|
238
|
+
| `BigInt` | Integer | 64-bit integer |
|
|
239
|
+
| `Point` | Point | Geographical coordinates (WGS-84) |
|
|
240
|
+
| `CartesianPoint` | Point | 2D/3D Cartesian coordinates |
|
|
241
|
+
|
|
242
|
+
### Relationships
|
|
243
|
+
|
|
244
|
+
```graphql
|
|
245
|
+
type Book @node {
|
|
246
|
+
# Singular relationship (one author per book)
|
|
247
|
+
author: Author! @relationship(type: "WRITTEN_BY", direction: OUT)
|
|
248
|
+
|
|
249
|
+
# Array relationship (many categories)
|
|
250
|
+
categories: [Category!]! @relationship(type: "IN_CATEGORY", direction: OUT)
|
|
251
|
+
|
|
252
|
+
# Relationship with edge properties
|
|
253
|
+
reviews: [Review!]! @relationship(
|
|
254
|
+
type: "HAS_REVIEW",
|
|
255
|
+
direction: OUT,
|
|
256
|
+
properties: "HasReview"
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
type HasReview @relationshipProperties {
|
|
261
|
+
createdAt: DateTime
|
|
262
|
+
rating: Int
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Interfaces
|
|
267
|
+
|
|
268
|
+
```graphql
|
|
269
|
+
interface Entity @node {
|
|
270
|
+
id: ID! @id
|
|
271
|
+
name: String!
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
type User @node implements Entity {
|
|
275
|
+
id: ID! @id
|
|
276
|
+
name: String!
|
|
277
|
+
email: String!
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
type Organization @node implements Entity {
|
|
281
|
+
id: ID! @id
|
|
282
|
+
name: String!
|
|
283
|
+
website: String
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Interface types can be queried polymorphically via `ogm.interfaceModel('Entity')`. Results include `__typename` to discriminate concrete types.
|
|
288
|
+
|
|
289
|
+
### Enums
|
|
290
|
+
|
|
291
|
+
```graphql
|
|
292
|
+
enum Status {
|
|
293
|
+
DRAFT
|
|
294
|
+
PUBLISHED
|
|
295
|
+
ARCHIVED
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
type Book @node {
|
|
299
|
+
status: Status
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Fulltext Indexes
|
|
304
|
+
|
|
305
|
+
```graphql
|
|
306
|
+
type Book @node
|
|
307
|
+
@fulltext(indexes: [
|
|
308
|
+
{ name: "BookSearch", fields: ["title", "description"] }
|
|
309
|
+
]) {
|
|
310
|
+
title: String!
|
|
311
|
+
description: String
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Relationship fulltext indexes are defined on `@relationshipProperties` types:
|
|
316
|
+
|
|
317
|
+
```graphql
|
|
318
|
+
type WrittenBy @relationshipProperties
|
|
319
|
+
@fulltext(indexes: [{ name: "AuthorRoleSearch", fields: ["role"] }]) {
|
|
320
|
+
role: String
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Query API
|
|
327
|
+
|
|
328
|
+
### Find Methods
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
const Book = ogm.model('Book');
|
|
332
|
+
|
|
333
|
+
// find — returns all matching nodes
|
|
334
|
+
const books = await Book.find({
|
|
335
|
+
where: { title_CONTAINS: 'Graph' },
|
|
336
|
+
select: { id: true, title: true },
|
|
337
|
+
options: { sort: [{ title: 'ASC' }], limit: 10, offset: 0 },
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// findFirst — returns first match or null
|
|
341
|
+
const book = await Book.findFirst({
|
|
342
|
+
where: { title_CONTAINS: 'Graph' },
|
|
343
|
+
select: { id: true, title: true },
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// findUnique — find by unique identifier or null
|
|
347
|
+
const book = await Book.findUnique({
|
|
348
|
+
where: { isbn: '978-1491930892' },
|
|
349
|
+
select: { id: true, title: true },
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// findFirstOrThrow — throws RecordNotFoundError if no match
|
|
353
|
+
const book = await Book.findFirstOrThrow({
|
|
354
|
+
where: { title: 'Exact Title' },
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// findUniqueOrThrow — throws RecordNotFoundError if not found
|
|
358
|
+
const book = await Book.findUniqueOrThrow({
|
|
359
|
+
where: { id: 'book-1' },
|
|
360
|
+
});
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### WHERE Operators
|
|
364
|
+
|
|
365
|
+
**Comparison operators:**
|
|
366
|
+
|
|
367
|
+
| Operator | Cypher | Example |
|
|
368
|
+
|---|---|---|
|
|
369
|
+
| *(equality)* | `=` | `{ name: 'Wireless Mouse' }` |
|
|
370
|
+
| `_NOT` | `<>` | `{ name_NOT: 'x' }` |
|
|
371
|
+
| `_IN` | `IN` | `{ id_IN: ['a', 'b'] }` |
|
|
372
|
+
| `_NOT_IN` | `NOT IN` | `{ id_NOT_IN: ['a'] }` |
|
|
373
|
+
| `_GT` | `>` | `{ price_GT: 10 }` |
|
|
374
|
+
| `_GTE` | `>=` | `{ price_GTE: 10 }` |
|
|
375
|
+
| `_LT` | `<` | `{ price_LT: 50 }` |
|
|
376
|
+
| `_LTE` | `<=` | `{ price_LTE: 50 }` |
|
|
377
|
+
|
|
378
|
+
**String operators:**
|
|
379
|
+
|
|
380
|
+
| Operator | Cypher | Example |
|
|
381
|
+
|---|---|---|
|
|
382
|
+
| `_CONTAINS` | `CONTAINS` | `{ name_CONTAINS: 'alb' }` |
|
|
383
|
+
| `_NOT_CONTAINS` | `NOT CONTAINS` | `{ name_NOT_CONTAINS: 'x' }` |
|
|
384
|
+
| `_STARTS_WITH` | `STARTS WITH` | `{ name_STARTS_WITH: 'A' }` |
|
|
385
|
+
| `_NOT_STARTS_WITH` | `NOT STARTS WITH` | `{ name_NOT_STARTS_WITH: 'X' }` |
|
|
386
|
+
| `_ENDS_WITH` | `ENDS WITH` | `{ name_ENDS_WITH: 'ol' }` |
|
|
387
|
+
| `_NOT_ENDS_WITH` | `NOT ENDS WITH` | `{ name_NOT_ENDS_WITH: 'x' }` |
|
|
388
|
+
| `_MATCHES` | `=~` (regex) | `{ name_MATCHES: '^A.*' }` |
|
|
389
|
+
|
|
390
|
+
**Relationship quantifiers:**
|
|
391
|
+
|
|
392
|
+
| Operator | Meaning | Example |
|
|
393
|
+
|---|---|---|
|
|
394
|
+
| *(bare key)* or `_SOME` | At least one related node matches | `{ categories_SOME: { name: 'Tech' } }` |
|
|
395
|
+
| `_NONE` | No related node matches | `{ categories_NONE: { name: 'Fiction' } }` |
|
|
396
|
+
| `_ALL` | Every related node matches | `{ categories_ALL: { name: 'Tech' } }` |
|
|
397
|
+
| `_SINGLE` | Exactly one related node matches | `{ categories_SINGLE: { name: 'Tech' } }` |
|
|
398
|
+
|
|
399
|
+
**Logical operators:**
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
await Book.find({
|
|
403
|
+
where: {
|
|
404
|
+
OR: [
|
|
405
|
+
{ title_CONTAINS: 'Graph' },
|
|
406
|
+
{ title_CONTAINS: 'Neo4j' },
|
|
407
|
+
],
|
|
408
|
+
NOT: { status: 'ARCHIVED' },
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**Null handling:**
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// Scalar null check
|
|
417
|
+
await Book.find({ where: { isbn: null } }); // WHERE n.isbn IS NULL
|
|
418
|
+
|
|
419
|
+
// Relationship existence
|
|
420
|
+
await Book.find({ where: { author: null } }); // NOT EXISTS pattern
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**Connection filters (with edge properties):**
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
await Book.find({
|
|
427
|
+
where: {
|
|
428
|
+
categoriesConnection_SOME: {
|
|
429
|
+
node: { name: 'Technology' },
|
|
430
|
+
edge: { isPrimary: true },
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Selection Modes
|
|
437
|
+
|
|
438
|
+
grafeo-ogm supports two mutually exclusive selection modes. Providing both `select` and `selectionSet` throws an error.
|
|
439
|
+
|
|
440
|
+
**Typed `select` API (recommended):**
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
const books = await Book.find({
|
|
444
|
+
select: {
|
|
445
|
+
id: true,
|
|
446
|
+
title: true,
|
|
447
|
+
author: { select: { name: true } }, // nested selection
|
|
448
|
+
categories: { where: { name_STARTS_WITH: 'Tech' }, select: { name: true } }, // with filter
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Simple relationship: true returns all scalar fields
|
|
453
|
+
const books = await Book.find({
|
|
454
|
+
select: { id: true, author: true },
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**String `selectionSet` (legacy, fully supported):**
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
const books = await Book.find({
|
|
462
|
+
selectionSet: `{
|
|
463
|
+
id
|
|
464
|
+
title
|
|
465
|
+
author { name }
|
|
466
|
+
categories { id name }
|
|
467
|
+
}`,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// Or set a default on the model instance
|
|
471
|
+
Book.selectionSet = `{ id title }`;
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Default (no selection):** Returns all scalar fields of the matched node.
|
|
475
|
+
|
|
476
|
+
### Sorting and Pagination
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
const books = await Book.find({
|
|
480
|
+
where: { status: 'PUBLISHED' },
|
|
481
|
+
options: {
|
|
482
|
+
sort: [{ title: 'ASC' }, { published: 'DESC' }],
|
|
483
|
+
limit: 20,
|
|
484
|
+
offset: 40,
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## Mutation API
|
|
492
|
+
|
|
493
|
+
### Create
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
const Book = ogm.model('Book');
|
|
497
|
+
|
|
498
|
+
// Create one or more nodes
|
|
499
|
+
const result = await Book.create({
|
|
500
|
+
input: [{
|
|
501
|
+
title: 'Graph Databases',
|
|
502
|
+
isbn: '978-1491930892',
|
|
503
|
+
author: { connect: { where: { node: { name: 'Jim Webber' } } } },
|
|
504
|
+
categories: {
|
|
505
|
+
create: [{ node: { name: 'Technology' }, edge: { isPrimary: true } }],
|
|
506
|
+
},
|
|
507
|
+
}],
|
|
508
|
+
});
|
|
509
|
+
// result: { info: { nodesCreated, relationshipsCreated }, books: [...] }
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Create Many
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
// Bulk create (scalar properties only, no nested ops)
|
|
516
|
+
const { count } = await Book.createMany({
|
|
517
|
+
data: [
|
|
518
|
+
{ title: 'Book A', isbn: '111' },
|
|
519
|
+
{ title: 'Book B', isbn: '222' },
|
|
520
|
+
],
|
|
521
|
+
skipDuplicates: true, // uses MERGE on unique fields
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Update
|
|
526
|
+
|
|
527
|
+
```typescript
|
|
528
|
+
const result = await Book.update({
|
|
529
|
+
where: { id: 'book-1' },
|
|
530
|
+
update: { title: 'Updated Title' },
|
|
531
|
+
connect: {
|
|
532
|
+
categories: [{ where: { node: { name: 'Science' } } }],
|
|
533
|
+
},
|
|
534
|
+
disconnect: {
|
|
535
|
+
categories: { where: { node: { name: 'Fiction' } } },
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Update Many
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
// Bulk update (returns count only, no connect/disconnect)
|
|
544
|
+
const { count } = await Book.updateMany({
|
|
545
|
+
where: { published_LT: '2020-01-01' },
|
|
546
|
+
data: { isbn: null },
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Delete
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
// Simple delete
|
|
554
|
+
await Book.delete({ where: { id: 'book-1' } });
|
|
555
|
+
|
|
556
|
+
// Cascade delete related nodes
|
|
557
|
+
await Book.delete({
|
|
558
|
+
where: { id: 'book-1' },
|
|
559
|
+
delete: {
|
|
560
|
+
categories: [{ where: { node: { name: 'Temp Category' } } }],
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### Delete Many
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
const { count } = await Book.deleteMany({
|
|
569
|
+
where: { title_CONTAINS: 'Draft' },
|
|
570
|
+
});
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### Upsert
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
// Create if not found, update if exists (scalar properties only)
|
|
577
|
+
const book = await Book.upsert({
|
|
578
|
+
where: { isbn: '978-1491930892' },
|
|
579
|
+
create: { title: 'Graph Databases', isbn: '978-1491930892' },
|
|
580
|
+
update: { title: 'Graph Databases, Updated' },
|
|
581
|
+
select: { id: true, title: true },
|
|
582
|
+
});
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Label Management
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
// Set, add, or remove labels on existing nodes
|
|
589
|
+
await Book.setLabels({
|
|
590
|
+
where: { id: 'book-1' },
|
|
591
|
+
addLabels: ['Published', 'Featured'],
|
|
592
|
+
removeLabels: ['Draft'],
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// Query with additional runtime labels
|
|
596
|
+
const activeBooks = await Book.find({
|
|
597
|
+
where: { title_CONTAINS: 'Graph' },
|
|
598
|
+
labels: ['Active'], // MATCH (n:`Book`:`Active`)
|
|
599
|
+
});
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### Nested Mutations
|
|
603
|
+
|
|
604
|
+
Create, connect, disconnect, and cascade-delete related nodes in a single operation:
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
await Book.create({
|
|
608
|
+
input: [{
|
|
609
|
+
title: 'Graph Databases',
|
|
610
|
+
categories: {
|
|
611
|
+
create: [{
|
|
612
|
+
node: { name: 'Technology' },
|
|
613
|
+
edge: { isPrimary: true }, // relationship properties
|
|
614
|
+
}],
|
|
615
|
+
},
|
|
616
|
+
author: {
|
|
617
|
+
connect: {
|
|
618
|
+
where: { node: { name: 'Jim Webber' } },
|
|
619
|
+
edge: { role: 'Primary Author' },
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
}],
|
|
623
|
+
});
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
---
|
|
627
|
+
|
|
628
|
+
## Advanced Features
|
|
629
|
+
|
|
630
|
+
### Fulltext Search
|
|
631
|
+
|
|
632
|
+
Requires a `@fulltext` directive on the node type and the index to exist in Neo4j. Use `assertIndexesAndConstraints` to create indexes automatically:
|
|
633
|
+
|
|
634
|
+
```typescript
|
|
635
|
+
await ogm.assertIndexesAndConstraints({ options: { create: true } });
|
|
636
|
+
|
|
637
|
+
const Book = ogm.model('Book');
|
|
638
|
+
|
|
639
|
+
// Basic search
|
|
640
|
+
const results = await Book.find({
|
|
641
|
+
fulltext: {
|
|
642
|
+
BookSearch: { phrase: 'graph databases' },
|
|
643
|
+
},
|
|
644
|
+
select: { id: true, title: true },
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// With score threshold
|
|
648
|
+
const highRelevance = await Book.find({
|
|
649
|
+
fulltext: {
|
|
650
|
+
BookSearch: { phrase: 'graph', score: 1.0 },
|
|
651
|
+
},
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
// Combined with where filters
|
|
655
|
+
const affordable = await Book.find({
|
|
656
|
+
fulltext: {
|
|
657
|
+
BookSearch: { phrase: 'art' },
|
|
658
|
+
},
|
|
659
|
+
where: { price_LT: 10 },
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Logical composition
|
|
663
|
+
const results = await Book.find({
|
|
664
|
+
fulltext: {
|
|
665
|
+
OR: [
|
|
666
|
+
{ BookSearch: { phrase: 'graph' } },
|
|
667
|
+
{ BookSearch: { phrase: 'database' } },
|
|
668
|
+
],
|
|
669
|
+
},
|
|
670
|
+
});
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
### Subgraph Operations
|
|
674
|
+
|
|
675
|
+
Clone or delete entire subgraphs using APOC procedures. Requires the APOC plugin installed in Neo4j.
|
|
676
|
+
|
|
677
|
+
```typescript
|
|
678
|
+
import { cloneSubgraph, deleteSubgraph } from 'grafeo-ogm';
|
|
679
|
+
|
|
680
|
+
// Clone a subgraph rooted at a node
|
|
681
|
+
const cloneResult = await cloneSubgraph(
|
|
682
|
+
'source-node-id',
|
|
683
|
+
{
|
|
684
|
+
ownedLabels: ['Book', 'Chapter', 'Section'],
|
|
685
|
+
ownedRelationships: ['HAS_CHAPTER', 'HAS_SECTION'],
|
|
686
|
+
referenceRelationships: [
|
|
687
|
+
{ fromLabel: 'Book', relationshipType: 'WRITTEN_BY', direction: 'OUT' },
|
|
688
|
+
],
|
|
689
|
+
},
|
|
690
|
+
transaction,
|
|
691
|
+
);
|
|
692
|
+
// cloneResult: { clonedRootId, idMapping: Map<original, cloned> }
|
|
693
|
+
|
|
694
|
+
// Delete a subgraph
|
|
695
|
+
const deleteResult = await deleteSubgraph(
|
|
696
|
+
'root-node-id',
|
|
697
|
+
{
|
|
698
|
+
ownedLabels: ['Book', 'Chapter'],
|
|
699
|
+
ownedRelationships: ['HAS_CHAPTER'],
|
|
700
|
+
referenceRelationships: [],
|
|
701
|
+
},
|
|
702
|
+
transaction,
|
|
703
|
+
);
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### Raw Cypher
|
|
707
|
+
|
|
708
|
+
```typescript
|
|
709
|
+
// Read query
|
|
710
|
+
const results = await ogm.$queryRaw<{ name: string; count: number }>(
|
|
711
|
+
'MATCH (a:Author)-[:WRITTEN_BY]-(b:Book) RETURN a.name AS name, count(b) AS count',
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
// Write operation
|
|
715
|
+
const { recordsAffected } = await ogm.$executeRaw(
|
|
716
|
+
'MATCH (b:Book) WHERE b.published < $cutoff DELETE b',
|
|
717
|
+
{ cutoff: '2020-01-01' },
|
|
718
|
+
);
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
### Transactions
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
// Callback style — interactive transaction
|
|
725
|
+
await ogm.$transaction(async (ctx) => {
|
|
726
|
+
await Book.create({ input: [{ title: 'Book A' }] }, { context: ctx });
|
|
727
|
+
await Book.update(
|
|
728
|
+
{ where: { id: 'book-1' }, update: { title: 'Updated' } },
|
|
729
|
+
{ context: ctx },
|
|
730
|
+
);
|
|
731
|
+
// Committed on success, rolled back on error
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
// Sequential style — array of operations
|
|
735
|
+
const [books, authors] = await ogm.$transaction([
|
|
736
|
+
(ctx) => Book.find({ select: { id: true } }, { context: ctx }),
|
|
737
|
+
(ctx) => Author.find({ select: { id: true } }, { context: ctx }),
|
|
738
|
+
]);
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### Interface Models
|
|
742
|
+
|
|
743
|
+
Query across all types implementing a shared interface:
|
|
744
|
+
|
|
745
|
+
```typescript
|
|
746
|
+
const Entity = ogm.interfaceModel('Entity');
|
|
747
|
+
|
|
748
|
+
// Returns both Users and Organizations with __typename
|
|
749
|
+
const entities = await Entity.find({
|
|
750
|
+
where: { name_CONTAINS: 'Acme' },
|
|
751
|
+
selectionSet: `{ id name }`,
|
|
752
|
+
});
|
|
753
|
+
// [{ id: '1', name: 'Acme Corp', __typename: 'Organization' }, ...]
|
|
754
|
+
|
|
755
|
+
// Count across all implementing types
|
|
756
|
+
const total = await Entity.count();
|
|
757
|
+
|
|
758
|
+
// Aggregate across all implementing types
|
|
759
|
+
const agg = await Entity.aggregate({
|
|
760
|
+
aggregate: { count: true },
|
|
761
|
+
});
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
Interface models are **read-only**. Use `ogm.model('User')` or `ogm.model('Organization')` for mutations.
|
|
765
|
+
|
|
766
|
+
### Aggregation
|
|
767
|
+
|
|
768
|
+
```typescript
|
|
769
|
+
const Book = ogm.model('Book');
|
|
770
|
+
|
|
771
|
+
// Simple count
|
|
772
|
+
const total = await Book.count();
|
|
773
|
+
const published = await Book.count({ where: { status: 'PUBLISHED' } });
|
|
774
|
+
|
|
775
|
+
// Field aggregation (min, max, average)
|
|
776
|
+
const priceStats = await Book.aggregate({
|
|
777
|
+
aggregate: { count: true, price: true },
|
|
778
|
+
});
|
|
779
|
+
// { count: 42, price: { min: 9.99, max: 59.99, average: 24.50 } }
|
|
780
|
+
|
|
781
|
+
// With where filter
|
|
782
|
+
const publishedStats = await Book.aggregate({
|
|
783
|
+
where: { status: 'PUBLISHED' },
|
|
784
|
+
aggregate: { count: true, price: true },
|
|
785
|
+
});
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
---
|
|
789
|
+
|
|
790
|
+
## Type Generation
|
|
791
|
+
|
|
792
|
+
Generate TypeScript types from your schema for compile-time safety. **No Neo4j driver connection needed at build time.**
|
|
793
|
+
|
|
794
|
+
### Usage
|
|
795
|
+
|
|
796
|
+
```typescript
|
|
797
|
+
import { generateTypes } from 'grafeo-ogm';
|
|
798
|
+
import fs from 'fs';
|
|
799
|
+
|
|
800
|
+
const result = await generateTypes({
|
|
801
|
+
typeDefs: fs.readFileSync('./schema.graphql', 'utf-8'),
|
|
802
|
+
outFile: './src/generated/ogm-types.ts',
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
console.log(`Generated ${result.typeCount} types in ${result.durationMs}ms`);
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
### Using Generated Types
|
|
809
|
+
|
|
810
|
+
```typescript
|
|
811
|
+
import { OGM } from 'grafeo-ogm';
|
|
812
|
+
import type { ModelMap } from './generated/ogm-types';
|
|
813
|
+
|
|
814
|
+
const ogm = new OGM<ModelMap>({ typeDefs, driver });
|
|
815
|
+
|
|
816
|
+
// ogm.model('Book') is now fully typed:
|
|
817
|
+
const Book = ogm.model('Book');
|
|
818
|
+
|
|
819
|
+
// Book.find() returns typed Book objects
|
|
820
|
+
// Book.create() accepts typed BookCreateInput
|
|
821
|
+
// Book.find({ where: ... }) validates BookWhere
|
|
822
|
+
// Book.find({ select: ... }) validates BookSelectFields
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
### Configuration
|
|
826
|
+
|
|
827
|
+
| Option | Type | Default | Description |
|
|
828
|
+
|---|---|---|---|
|
|
829
|
+
| `typeDefs` | `string` | *(required)* | Raw GraphQL schema string |
|
|
830
|
+
| `outFile` | `string` | *(required)* | Output file path |
|
|
831
|
+
| `config.packageName` | `string` | `'grafeo-ogm'` | Package name in generated import statements |
|
|
832
|
+
| `config.stringMatchesFilter` | `boolean` | `true` | Enable `_MATCHES` regex operator in where types |
|
|
833
|
+
| `config.formatOutput` | `boolean` | `true` | Format output with Prettier |
|
|
834
|
+
| `config.prettierConfig` | `object` | `undefined` | Custom Prettier configuration |
|
|
835
|
+
| `config.header` | `string` | `undefined` | Custom header comment prepended to the file |
|
|
836
|
+
|
|
837
|
+
### Custom Package Name
|
|
838
|
+
|
|
839
|
+
If you install grafeo-ogm under an npm alias:
|
|
840
|
+
|
|
841
|
+
```typescript
|
|
842
|
+
await generateTypes({
|
|
843
|
+
typeDefs,
|
|
844
|
+
outFile: './src/generated/ogm-types.ts',
|
|
845
|
+
config: { packageName: '@myorg/ogm' },
|
|
846
|
+
});
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
### What Gets Generated
|
|
850
|
+
|
|
851
|
+
The generator runs 16 specialized emitters to produce:
|
|
852
|
+
|
|
853
|
+
- **Node types** -- TypeScript interfaces for each `@node` type (e.g., `Book`, `Author`)
|
|
854
|
+
- **Where types** -- All operator suffixes per field (e.g., `BookWhere` with `title_CONTAINS`, `id_IN`)
|
|
855
|
+
- **CreateInput / UpdateInput** -- Typed mutation inputs with nested relationship operations
|
|
856
|
+
- **ConnectInput / DisconnectInput** -- Relationship operation inputs
|
|
857
|
+
- **SelectFields** -- Types for the programmatic `select: {}` API
|
|
858
|
+
- **SelectResult** -- Return types narrowed by selection
|
|
859
|
+
- **ConnectionWhere** -- Edge property filtering types
|
|
860
|
+
- **SortInput** -- Sort options with direction constraints
|
|
861
|
+
- **AggregateSelection** -- Aggregation return types
|
|
862
|
+
- **MutationResponse** -- Typed mutation return shapes
|
|
863
|
+
- **FulltextInput** -- Fulltext search input types
|
|
864
|
+
- **ModelMap / InterfaceModelMap** -- Generic maps for `ogm.model()` and `ogm.interfaceModel()`
|
|
865
|
+
|
|
866
|
+
---
|
|
867
|
+
|
|
868
|
+
## Testing Utilities
|
|
869
|
+
|
|
870
|
+
Import from `grafeo-ogm/testing`:
|
|
871
|
+
|
|
872
|
+
```typescript
|
|
873
|
+
import {
|
|
874
|
+
CypherAssert,
|
|
875
|
+
Neo4jRecordFactory,
|
|
876
|
+
SelectionSetFactory,
|
|
877
|
+
} from 'grafeo-ogm/testing';
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
### CypherAssert
|
|
881
|
+
|
|
882
|
+
Assert Cypher queries contain expected patterns:
|
|
883
|
+
|
|
884
|
+
```typescript
|
|
885
|
+
// Check that a Cypher string contains a fragment
|
|
886
|
+
CypherAssert.assertContains(cypher, 'MATCH (n:`Book`)');
|
|
887
|
+
|
|
888
|
+
// Check parameter values
|
|
889
|
+
CypherAssert.assertParams(params, { param0: 'Graph Databases' });
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### Neo4jRecordFactory
|
|
893
|
+
|
|
894
|
+
Build mock Neo4j records for unit tests without a running database:
|
|
895
|
+
|
|
896
|
+
```typescript
|
|
897
|
+
const records = Neo4jRecordFactory.create([
|
|
898
|
+
{ id: '1', title: 'Test Book', published: '2024-01-01' },
|
|
899
|
+
{ id: '2', title: 'Another Book', published: '2024-06-15' },
|
|
900
|
+
]);
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
### SelectionSetFactory
|
|
904
|
+
|
|
905
|
+
Build selection sets programmatically:
|
|
906
|
+
|
|
907
|
+
```typescript
|
|
908
|
+
const selectionSet = SelectionSetFactory.build(`{
|
|
909
|
+
id
|
|
910
|
+
title
|
|
911
|
+
author { name }
|
|
912
|
+
}`);
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
---
|
|
916
|
+
|
|
917
|
+
## Security
|
|
918
|
+
|
|
919
|
+
grafeo-ogm implements 7 layered security controls. Every user-provided value passes through validation before it reaches the Cypher string.
|
|
920
|
+
|
|
921
|
+
### 1. Parameterization
|
|
922
|
+
|
|
923
|
+
All user values become `$paramN` references. No value is ever string-interpolated into Cypher:
|
|
924
|
+
|
|
925
|
+
```typescript
|
|
926
|
+
// Input: { name: "'; DROP DATABASE neo4j; //" }
|
|
927
|
+
// Cypher: n.name = $param0
|
|
928
|
+
// Params: { param0: "'; DROP DATABASE neo4j; //" }
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
### 2. Identifier Validation
|
|
932
|
+
|
|
933
|
+
Field names, sort fields, and aggregate fields are validated against `/^[a-zA-Z_][a-zA-Z0-9_]*$/`. Any identifier containing special characters is rejected before query compilation.
|
|
934
|
+
|
|
935
|
+
### 3. Label Validation
|
|
936
|
+
|
|
937
|
+
Labels used in `MATCH`, `SET`, and `REMOVE` are validated with the same regex, preventing label injection attacks.
|
|
938
|
+
|
|
939
|
+
### 4. Prototype Pollution Prevention
|
|
940
|
+
|
|
941
|
+
Keys `__proto__`, `constructor`, and `prototype` are blocked in all `where` and `update` inputs. The ResultMapper uses `Object.create(null)` for all output objects to prevent prototype chain attacks.
|
|
942
|
+
|
|
943
|
+
### 5. Lucene Escaping
|
|
944
|
+
|
|
945
|
+
Special Lucene characters (`+ - && || ! ( ) { } [ ] ^ " ~ * ? : \ /`) are escaped before being passed to fulltext queries.
|
|
946
|
+
|
|
947
|
+
### 6. Depth Limiting
|
|
948
|
+
|
|
949
|
+
- WhereCompiler: max recursion depth of 10
|
|
950
|
+
- SelectionCompiler: configurable max relationship traversal depth (default 5)
|
|
951
|
+
|
|
952
|
+
Prevents denial-of-service via deeply nested or circular inputs.
|
|
953
|
+
|
|
954
|
+
### 7. Sort Direction Validation
|
|
955
|
+
|
|
956
|
+
Sort directions are strictly validated as `"ASC"` or `"DESC"`. Any other value -- including injected Cypher fragments -- is rejected.
|
|
957
|
+
|
|
958
|
+
### Best Practices
|
|
959
|
+
|
|
960
|
+
- Always use the OGM's query methods instead of raw Cypher when possible
|
|
961
|
+
- Use `generateTypes()` for compile-time validation of inputs
|
|
962
|
+
- Set appropriate depth limits for your schema's relationship density
|
|
963
|
+
- Use `$transaction()` for multi-step operations to maintain consistency
|
|
964
|
+
- Keep `@unique` constraints on fields used for `findUnique` lookups
|
|
965
|
+
|
|
966
|
+
---
|
|
967
|
+
|
|
968
|
+
## Migration from @neo4j/graphql-ogm
|
|
969
|
+
|
|
970
|
+
grafeo-ogm is a **drop-in replacement** for the deprecated `@neo4j/graphql-ogm`. Existing code works without changes, and you can adopt new features incrementally.
|
|
971
|
+
|
|
972
|
+
### Step 1: Replace the dependency
|
|
973
|
+
|
|
974
|
+
```bash
|
|
975
|
+
npm uninstall @neo4j/graphql-ogm
|
|
976
|
+
npm install grafeo-ogm
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
### Step 2: Update imports
|
|
980
|
+
|
|
981
|
+
```typescript
|
|
982
|
+
// Before
|
|
983
|
+
import { OGM } from '@neo4j/graphql-ogm';
|
|
984
|
+
|
|
985
|
+
// After
|
|
986
|
+
import { OGM } from 'grafeo-ogm';
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
### Step 3: Update type generation
|
|
990
|
+
|
|
991
|
+
Replace your existing type generation script to use `generateTypes()` from grafeo-ogm. No Neo4j driver connection is needed at build time.
|
|
992
|
+
|
|
993
|
+
### Step 4 (optional): Adopt new features
|
|
994
|
+
|
|
995
|
+
These are incremental improvements you can adopt at your own pace:
|
|
996
|
+
|
|
997
|
+
| Old Pattern | New Alternative | Benefit |
|
|
998
|
+
|---|---|---|
|
|
999
|
+
| `selectionSet` strings | `select: { field: true }` | Compile-time type checking |
|
|
1000
|
+
| `find()` + manual null check | `findFirstOrThrow()` | Throws `RecordNotFoundError` automatically |
|
|
1001
|
+
| `find({ where, options: { limit: 1 } })` | `findFirst({ where })` | Cleaner API, returns `T \| null` |
|
|
1002
|
+
| Manual bulk loops | `createMany()`, `updateMany()`, `deleteMany()` | Single query, returns `{ count }` |
|
|
1003
|
+
| Manual label management | `setLabels()`, `labels` parameter | Built-in label add/remove/filter |
|
|
1004
|
+
|
|
1005
|
+
### What's the Same
|
|
1006
|
+
|
|
1007
|
+
- `OGM` constructor signature: `{ typeDefs, driver }` -- identical
|
|
1008
|
+
- `ogm.model('Name')` -- same API
|
|
1009
|
+
- `model.find()`, `model.create()`, `model.update()`, `model.delete()` -- same signatures
|
|
1010
|
+
- `selectionSet` -- fully supported
|
|
1011
|
+
- `init()` -- still works (now a no-op since schema parsing is synchronous)
|
|
1012
|
+
- `assertIndexesAndConstraints()` -- same behavior
|
|
1013
|
+
|
|
1014
|
+
### What Changed (Under the Hood)
|
|
1015
|
+
|
|
1016
|
+
- Queries use **pattern comprehensions** instead of `OPTIONAL MATCH` -- better performance, same results
|
|
1017
|
+
- Mutations use **CALL subquery isolation** -- prevents Cartesian products in connect/disconnect
|
|
1018
|
+
- All identifiers are **validated and escaped** -- injection-safe by default
|
|
1019
|
+
- Type generation is **modular and driver-free** -- smaller output, faster builds
|
|
1020
|
+
|
|
1021
|
+
---
|
|
1022
|
+
|
|
1023
|
+
## API Reference
|
|
1024
|
+
|
|
1025
|
+
### OGM
|
|
1026
|
+
|
|
1027
|
+
| Method | Description |
|
|
1028
|
+
|---|---|
|
|
1029
|
+
| `new OGM(config)` | Create an OGM instance with `{ typeDefs, driver, logger?, features? }` |
|
|
1030
|
+
| `model(name)` | Get a typed Model for a node type |
|
|
1031
|
+
| `interfaceModel(name)` | Get a typed InterfaceModel for an interface type |
|
|
1032
|
+
| `$queryRaw(cypher, params?)` | Execute a read query and return mapped results |
|
|
1033
|
+
| `$executeRaw(cypher, params?)` | Execute a write operation and return affected counts |
|
|
1034
|
+
| `$transaction(fn)` | Execute operations in an atomic transaction |
|
|
1035
|
+
| `assertIndexesAndConstraints(options?)` | Create fulltext indexes and uniqueness constraints |
|
|
1036
|
+
| `close()` | Clear caches and release references |
|
|
1037
|
+
|
|
1038
|
+
### Model
|
|
1039
|
+
|
|
1040
|
+
| Method | Returns | Description |
|
|
1041
|
+
|---|---|---|
|
|
1042
|
+
| `find(options?)` | `T[]` | Find all nodes matching filters |
|
|
1043
|
+
| `findFirst(options?)` | `T \| null` | Find first matching node |
|
|
1044
|
+
| `findUnique(options)` | `T \| null` | Find by unique identifier |
|
|
1045
|
+
| `findFirstOrThrow(options?)` | `T` | Find first match or throw `RecordNotFoundError` |
|
|
1046
|
+
| `findUniqueOrThrow(options)` | `T` | Find by unique identifier or throw |
|
|
1047
|
+
| `create(options)` | `MutationResponse` | Create one or more nodes with optional relationships |
|
|
1048
|
+
| `createMany(options)` | `{ count }` | Bulk create nodes |
|
|
1049
|
+
| `update(options)` | `MutationResponse` | Update matched nodes with connect/disconnect |
|
|
1050
|
+
| `updateMany(options)` | `{ count }` | Bulk update matched nodes |
|
|
1051
|
+
| `delete(options)` | `DeleteInfo` | Delete matched nodes with optional cascade |
|
|
1052
|
+
| `deleteMany(options)` | `{ count }` | Bulk delete matched nodes |
|
|
1053
|
+
| `upsert(options)` | `T` | Create if not found, update if exists |
|
|
1054
|
+
| `count(options?)` | `number` | Count matching nodes |
|
|
1055
|
+
| `aggregate(options)` | `AggregateResult` | Aggregate values (min, max, avg, count) |
|
|
1056
|
+
| `setLabels(options)` | `void` | Add or remove labels on matching nodes |
|
|
1057
|
+
|
|
1058
|
+
### InterfaceModel
|
|
1059
|
+
|
|
1060
|
+
| Method | Returns | Description |
|
|
1061
|
+
|---|---|---|
|
|
1062
|
+
| `find(options?)` | `T[]` | Find nodes across all implementing types |
|
|
1063
|
+
| `findFirst(options?)` | `T \| null` | Find first match |
|
|
1064
|
+
| `findUnique(options)` | `T \| null` | Find by unique identifier |
|
|
1065
|
+
| `findFirstOrThrow(options?)` | `T` | Find first match or throw |
|
|
1066
|
+
| `findUniqueOrThrow(options)` | `T` | Find by unique identifier or throw |
|
|
1067
|
+
| `count(options?)` | `number` | Count matching nodes |
|
|
1068
|
+
| `aggregate(options)` | `AggregateResult` | Aggregate across implementing types |
|
|
1069
|
+
|
|
1070
|
+
### generateTypes(options)
|
|
1071
|
+
|
|
1072
|
+
| Option | Type | Default | Description |
|
|
1073
|
+
|---|---|---|---|
|
|
1074
|
+
| `typeDefs` | `string` | *(required)* | Raw GraphQL schema string |
|
|
1075
|
+
| `outFile` | `string` | *(required)* | Output file path |
|
|
1076
|
+
| `config.packageName` | `string` | `'grafeo-ogm'` | Package name in generated imports |
|
|
1077
|
+
| `config.stringMatchesFilter` | `boolean` | `true` | Enable `_MATCHES` operator |
|
|
1078
|
+
| `config.formatOutput` | `boolean` | `true` | Format with Prettier |
|
|
1079
|
+
| `config.prettierConfig` | `object` | — | Custom Prettier config |
|
|
1080
|
+
| `config.header` | `string` | — | Custom header comment |
|
|
1081
|
+
|
|
1082
|
+
Returns `GenerateTypesResult`:
|
|
1083
|
+
|
|
1084
|
+
| Field | Type | Description |
|
|
1085
|
+
|---|---|---|
|
|
1086
|
+
| `outputPath` | `string` | Absolute path of the written file |
|
|
1087
|
+
| `typeCount` | `number` | Number of exported types/interfaces/enums |
|
|
1088
|
+
| `fileSize` | `number` | File size in bytes |
|
|
1089
|
+
| `durationMs` | `number` | Generation duration in milliseconds |
|
|
1090
|
+
| `warnings` | `GeneratorWarning[]` | Non-fatal warnings encountered |
|
|
1091
|
+
|
|
1092
|
+
For detailed usage examples, see the [`examples/`](./examples/) directory.
|
|
1093
|
+
|
|
1094
|
+
---
|
|
1095
|
+
|
|
1096
|
+
## Contributing
|
|
1097
|
+
|
|
1098
|
+
Contributions are welcome. Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
1099
|
+
|
|
1100
|
+
```bash
|
|
1101
|
+
# Clone and install
|
|
1102
|
+
git clone https://github.com/aleguzman/grafeo-ogm.git
|
|
1103
|
+
cd grafeo-ogm
|
|
1104
|
+
pnpm install
|
|
1105
|
+
|
|
1106
|
+
# Run tests
|
|
1107
|
+
pnpm test
|
|
1108
|
+
|
|
1109
|
+
# Lint and format
|
|
1110
|
+
pnpm run lint
|
|
1111
|
+
pnpm run format
|
|
1112
|
+
|
|
1113
|
+
# Build
|
|
1114
|
+
pnpm run build
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
---
|
|
1118
|
+
|
|
1119
|
+
## License
|
|
1120
|
+
|
|
1121
|
+
[MIT](LICENSE)
|