-
Notifications
You must be signed in to change notification settings - Fork 3
Description
Having an ORM-style layer on top of Golem Base could greatly simplify the experience for newcomers who don't need the full configurability and feature set of the TypeScript SDK. The goal would be to abstract away as much complexity as possible and instead offer a library with a set of intuitive methods that feel familiar to developers migrating from other common libraries.
The more familiar the interface is, the easier it will be for people to get started and the more likely they are to adopt it.
With that in mind, I came up with an idea.
I created a README-style example to show how this could work. I started building it, using Django-style models as a mental reference since I primarily work in Python. However, I ran into issues submitting transactions and eventually had to put the project aside due to time constraints.
Below is the general concept I had in mind (written using AI to help map my mind from Django to Typescript logic)
ORM README
Link to WIP package that was built upon the typescript-sdk
Golem Base ORM (Django Style!) - golem-orm
Hey Django Devs! 👋 Welcome to golem-orm. If you love how Django's ORM makes database interactions a breeze, you'll appreciate what this library does for Golem Base. We're bringing that familiar, productive model-centric approach to the Golem Network's decentralized data storage.
Think of it like this: Golem Base is your new kind of "database," and golem-orm is your Django-like toolkit to talk to it without getting bogged down in the low-level details.
Core Idea (The Django Analogy)
- Models (like
models.Model): You define your data structures as TypeScript classes, similar to Django models. - Manager/Repository (like
YourModel.objects): Each model gets a "manager" (we call it aRepository) that provides methods to create, retrieve, update, and delete your data (e.g.,CoinProfile.objects.create(...)becomescoinProfileRepository.create(...)). - Querying (like QuerySets): We aim for a simple way to fetch data based on criteria, abstracting Golem's annotation queries.
The goal? Write less boilerplate, focus on your application logic, and let the ORM handle the Golem-specific "magic" (like entity keys, data formats, and annotations).
Features
- Django-esque Model Definitions: Use TypeScript classes and decorators that feel intuitive.
- Automatic Field Mapping: Define your fields; the ORM handles how they're stored in Golem.
- Simplified CRUD:
create(),findById(),update(),delete()methods on your model's "manager". - Transparent Golem
entityKeyHandling: You work with your model's natural IDs (like a primary key); the ORM manages Golem's uniqueentityKeys. - Automatic Data Conversion: Your TypeScript objects are automatically prepared for Golem and converted back when fetched.
- Annotation Abstraction: Define which fields should be "indexed" (annotated in Golem terms) for efficient lookups, much like
db_index=True.
Installation
# In your project (e.g., where your data-ingestion-service or similar app lives)
# Make sure your main package.json can resolve workspace packages.
# Add golem-orm and its peer golem-base-sdk
# (Assuming your project uses yarn, npm, or pnpm for workspaces)
# Example: yarn add golem-orm golem-base-sdkEnsure reflect-metadata is also installed in your main application as it's a peer dependency for decorator support:
yarn add reflect-metadata (and import it once at the top of your main application file, e.g., index.ts: import 'reflect-metadata';)
Setup: Getting Your "Manager"
-
The Golem Connection (like Django's
DATABASESsetting):
First, you need to tell the ORM how to connect to Golem Base. This involves setting up the underlyinggolem-base-sdkclient.// (Conceptual - place in a central spot like 'golemClient.ts' or 'config.ts') import { createClient as createGolemBaseClient, GolemBaseClient, AccountData } from 'golem-base-sdk'; import { GolemOrmClient } from 'golem-orm'; import { Logger } from 'tslog'; import 'reflect-metadata'; // Important: Import once in your app's entry point // --- SDK Client Setup (Your Golem "Database" Connection) --- const CHAIN_ID = 1337; // Your target chain ID const RPC_URL = 'http://localhost:8545'; // Your Golem node RPC const WS_URL = 'ws://localhost:8546'; // Your Golem node WebSocket // IMPORTANT: Securely manage your private key! This is just an example. const YOUR_PRIVATE_KEY_BYTES = new Uint8Array([...]); // e.g., from a hex string or env var const ACCOUNT_DATA: AccountData = { tag: 'privatekey', data: YOUR_PRIVATE_KEY_BYTES }; const logger = new Logger({ name: 'MyAppLogger' }); let golemOrmInstance: GolemOrmClient; export async function getOrm(): Promise<GolemOrmClient> { if (!golemOrmInstance) { const sdkClient = await createGolemBaseClient( CHAIN_ID, ACCOUNT_DATA, RPC_URL, WS_URL, logger ); golemOrmInstance = new GolemOrmClient(sdkClient); console.log('Golem ORM Initialized!'); } return golemOrmInstance; }
Defining Your Models (like models.py)
Create classes for your data structures and use decorators to tell the ORM how they map to Golem entities.
// src/models/coin.ts (or wherever you keep your "Django models")
import { Entity, PrimaryKey, Property, AnnotationField } from 'golem-orm';
@Entity({ golemEntityType: 'CoinProfile', appKeyPrefix: 'coin' }) // Like a Django model class
export class CoinProfile {
@PrimaryKey() // Like Django's primary_key=True
id: string; // e.g., "bitcoin", "ethereum" - your natural key
@AnnotationField({ golemKey: 'name_idx', type: 'string' }) // Like db_index=True for querying
name: string;
@Property() // A regular data field
symbol: string;
@Property()
description?: string;
@AnnotationField({ golemKey: 'rank_idx', type: 'numeric' })
marketCapRank?: number;
// The ORM automatically adds Golem annotations for:
// - 'type': 'CoinProfile'
// - 'appKey': 'coin-bitcoin' (if id is 'bitcoin')
// These help in organizing and finding data within Golem.
}Using Your Models (The Fun Part!)
This is where it feels like Django's ORM. You get a "Repository" (think of it as YourModel.objects) for each model.
// src/appLogic.ts (or your Django 'views.py' / 'services.py' equivalent)
import { getOrm } from './config'; // Your setup file
import { CoinProfile } from './models/coin';
async function manageCoinData() {
const orm = await getOrm();
const coinRepo = orm.getRepository(CoinProfile); // This is like CoinProfile.objects
// --- Create (like YourModel.objects.create()) ---
console.log('Creating a new coin...');
const newCoin = await coinRepo.create({
id: 'supercoin',
name: 'Super Coin',
symbol: 'SPC',
marketCapRank: 1,
description: 'The next big thing!',
});
console.log('Created:', newCoin);
// `newCoin` is your TypeScript object, now persisted in Golem.
// --- Retrieve (like YourModel.objects.get()) ---
console.log('\nFetching coin by its ID (supercoin)...');
const fetchedCoin = await coinRepo.findById('supercoin'); // Find by your natural primary key
if (fetchedCoin) {
console.log('Found:', fetchedCoin.name, fetchedCoin.description);
} else {
console.log('Coin not found!');
}
// --- Update (like instance.save()) ---
if (fetchedCoin) {
console.log('\nUpdating coin description...');
const updatedCoin = await coinRepo.update('supercoin', {
description: 'The absolute, undisputed next big thing!',
});
console.log('Updated Description:', updatedCoin?.description);
}
// --- Query (like YourModel.objects.filter()) ---
console.log('\nFinding top-ranked coins (rank <= 5)...');
// The query API will allow filtering based on @AnnotationField decorated fields
const topCoins = await coinRepo.query({
numericAnnotations: [{ key: 'rank_idx', operator: '<=', value: 5 }],
// We can add sorting options here too, e.g., orderBy: { field: 'rank_idx', direction: 'ASC' }
});
console.log(`Found ${topCoins.length} top coins:`);
topCoins.forEach(coin => console.log(`- ${coin.name} (Rank: ${coin.marketCapRank})`));
// --- Delete (like instance.delete()) ---
console.log('\nDeleting supercoin...');
await coinRepo.delete('supercoin');
const verifyDeleted = await coinRepo.findById('supercoin');
console.log('Supercoin exists after delete?', !!verifyDeleted); // Should be false
}
// Run the demo
// manageCoinData().catch(error => console.error('Error in demo:', error));How it's "Django-like"
- Active Record Pattern (ish): While repositories are closer to Data Mappers, the interaction with model instances after fetching them will feel familiar.
- Declarative Models: Define structure and some database-like behavior (indexing via annotations) right on the class.
- Abstraction of Low-Level Details: You don't deal with Golem's
entityKeyhex strings,Uint8Arraydata, or manually crafting annotation lists. You work withstring,number, and your TypeScript objects.
This ORM aims to make Golem Base development more accessible and productive, especially for those used to the convenience of mature ORMs like Django's.
Disclaimer: Decorator names (@Entity, @PrimaryKey, etc.) and specific method signatures are illustrative and will be refined during the actual implementation based on the ORM_PLAN.md.