Cypher Integration
Storage participates in the same Cypher dialect used for the rest of the graph - buckets are nodes, objects are nodes, and dedicated helper functions surface URLs, sizes, and image metadata so reports can pull everything in a single query.
Bucket DDL
-- Create CREATE BUCKET avatars PUBLIC = true, FILE_SIZE_LIMIT = 5242880, ALLOWED_MIME_TYPES = ["image/png", "image/jpeg"]; -- Update ALTER BUCKET avatars SET PUBLIC = false; -- Drop (bucket must be empty) DROP BUCKET avatars; -- Inspect SHOW BUCKETS;
Matching objects
-- Every avatar over 1 MB. MATCH (o:`storage.Object`) WHERE o.bucket_id = "avatars" AND o.size > 1048576 RETURN o.path, file_size(o), file_mime(o) ORDER BY o.size DESC LIMIT 20;
Helper functions
| Function | Returns | Use |
|---|---|---|
| file_url(o) | string | Authenticated download URL |
| signed_url(o, ttl_secs) | string | Mint a signed URL inline |
| file_size(o) | int | Byte size |
| file_mime(o) | string | MIME type |
| file_etag(o) | string | Strong ETag |
| file_hash(o) | string | SHA-256 content hash |
| file_exists(bucket, path) | bool | Cheap existence check |
| bucket_size(bucket) | int | Sum of object sizes in a bucket |
| image_metadata(o) | map | {width, height, format, color_space} |
Attaching objects to user-defined nodes
A common pattern: store the object as a node, then connect it with a typed relationship to a domain object (User, Document, Product…). RLS, sync, and triggers all see this just like any other edge.
MATCH (u:User {id: $userId})
MATCH (o:`storage.Object` {bucket_id: "avatars", path: $path})
CREATE (u)-[:AVATAR]->(o);
-- Later: fetch every user's avatar URL in one round-trip.
MATCH (u:User)-[:AVATAR]->(o:`storage.Object`)
RETURN u.name, file_url(o) AS avatar_url;