Skip to content

Cloud Spanner Graph driver for GQL (Graph Query Language) with Effect.ts

License

Notifications You must be signed in to change notification settings

artimath/effect-gql-spanner

Repository files navigation

effect-gql-spanner

Cloud Spanner Graph driver implementing the GQL (Graph Query Language) abstraction with Effect.ts.

Features

  • 🎯 Complete GQL implementation for Cloud Spanner Graph
  • πŸ“Š Type-safe graph queries with schema validation
  • πŸ”„ Schemaless node/edge operations with runtime type checking
  • πŸ§ͺ Production-tested in Lever monorepo
  • ⚑ Built on effect-gql, effect-sql-spanner, and effect-sql-googlesql

Installation

pnpm add effect-gql-spanner effect-gql effect-sql-spanner effect @effect/sql

Quick Start

import * as GqlClient from "effect-gql-spanner/GqlClient"
import * as Effect from "effect/Effect"

const program = Effect.gen(function* () {
  const gql = yield* GqlClient.GqlClient

  // Execute GQL query
  const users = yield* gql.execute(
    `MATCH (u:User)-[:FOLLOWS]->(f:User) 
     WHERE u.id = @userId 
     RETURN f`,
    ["user-123"]
  )

  return users
})

Core Modules

GqlClient

Spanner Graph client implementing the GqlClient interface:

import * as GqlClient from "effect-gql-spanner/GqlClient"
import * as Layer from "effect/Layer"

const SpannerGqlLayer = GqlClient.layer({
  projectId: "my-project",
  instanceId: "my-instance",
  databaseId: "my-database"
})

const program = Effect.provide(myProgram, SpannerGqlLayer)

GraphSchema

Type-safe graph query constructors:

import * as GraphSchema from "effect-gql-spanner/GraphSchema"
import * as Schema from "effect/Schema"

const findFollowers = GraphSchema.gqlAll({
  Request: Schema.Struct({ userId: Schema.String }),
  Result: Schema.Struct({ 
    id: Schema.String, 
    name: Schema.String 
  }),
  execute: (req) => gql.execute(
    `MATCH (u:User {id: @userId})-[:FOLLOWS]->(f) RETURN f`,
    [req.userId]
  )
})

SchemalessSchema

Runtime-validated graph operations without compile-time schemas:

import * as SchemalessSchema from "effect-gql-spanner/SchemalessSchema"

// Insert node with runtime validation
yield* SchemalessSchema.insertNode({
  label: "User",
  properties: {
    id: "user-123",
    email: "alice@example.com"
  }
})

// Query with runtime schema
const users = yield* SchemalessSchema.findNodes({
  label: "User",
  filter: { role: "admin" }
})

GraphNodes

High-level node operations:

import * as GraphNodes from "effect-gql-spanner/GraphNodes"

// Insert typed node
yield* GraphNodes.insert({
  label: "User",
  key: "user-123",
  properties: { name: "Alice", verified: true }
})

// Update node
yield* GraphNodes.update({
  label: "User",
  key: "user-123",
  properties: { lastLogin: new Date() }
})

GraphEdges

High-level edge operations:

import * as GraphEdges from "effect-gql-spanner/GraphEdges"

// Create relationship
yield* GraphEdges.insert({
  label: "FOLLOWS",
  sourceKey: "user-123",
  targetKey: "user-456",
  properties: { since: new Date() }
})

GraphPaths

Path traversal helpers:

import * as GraphPaths from "effect-gql-spanner/GraphPaths"

// Find paths between nodes
const paths = yield* GraphPaths.findPaths({
  startLabel: "User",
  startKey: "user-123",
  endLabel: "User",
  endKey: "user-456",
  maxDepth: 5
})

GqlResolver

Query result resolution with schema validation:

import * as GqlResolver from "effect-gql-spanner/GqlResolver"

const resolver = GqlResolver.make({
  "User": Schema.Struct({ id: Schema.String, email: Schema.String }),
  "Post": Schema.Struct({ id: Schema.String, title: Schema.String })
})

// Automatically validates and decodes based on node labels
const results = yield* resolver.resolve(queryResults)

Configuration

interface SpannerGqlClientConfig {
  projectId: string
  instanceId: string
  databaseId: string
  emulatorHost?: string
  transformQueryNames?: (name: string) => string
  transformResultNames?: (name: string) => string
}

Testing

Emulator Testing (Default)

Tests run against Spanner emulator by default:

# Run tests with emulator (default)
pnpm test

# Watch mode with emulator
pnpm test:watch

The emulator is automatically configured via SPANNER_EMULATOR_HOST=localhost:9010.

Live Database Testing

To test against a live Spanner instance:

# Run tests against live Spanner
pnpm test:live

Make sure to configure credentials before running live tests.

Test Layer Setup

import * as Testing from "effect-sql-spanner/testing"

// Emulator layer (creates isolated instance per test)
const testLayer = Testing.EmulatorPerTestLayer

it.effect("should query graph", () =>
  Effect.gen(function* () {
    const gql = yield* GqlClient.GqlClient
    
    yield* gql.execute(
      `CREATE (u:User {id: @id, name: @name})`,
      ["user-123", "Alice"]
    )
    
    const users = yield* gql.execute(
      `MATCH (u:User {id: @id}) RETURN u`,
      ["user-123"]
    )
    
    assert.strictEqual(users.length, 1)
  }).pipe(Effect.provide(testLayer))
)

Architecture

effect-gql-spanner bridges three layers:

  1. effect-gql: Abstract GQL client interface
  2. effect-sql-spanner: Spanner SQL driver with GoogleSQL compilation
  3. effect-sql-googlesql: GoogleSQL query compilation and parameter typing

This creates a type-safe, Effect-native way to work with Spanner Graph using standard GQL syntax.

License

Apache-2.0

Author

Ryan Hunter (@artimath)

Links

About

Cloud Spanner Graph driver for GQL (Graph Query Language) with Effect.ts

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published