TypeScript SDK
@loguro/datasets is the typed npm package for dataset workflows.
Use it when you want schema definition, ingest payloads, filters, aggregates, and query rows to be checked by TypeScript instead of hand-writing raw JSON requests.
Install
npm i @loguro/datasets Define a schema
import {
DatasetIngestClient,
DatasetQueryClient,
defineDatasetSchema
} from '@loguro/datasets';
const Orders = defineDatasetSchema({
country: 'string',
plan: 'string',
amount: 'number',
paid: 'boolean',
signup_at: 'timestamp'
}); The schema object is the source of truth. The SDK infers record fields, filter values, aggregate compatibility, and selected row shape from it.
Do not include timestamp or context in defineDatasetSchema. They are standard fields required on every record.
Clients
Datasets use two auth surfaces, so the SDK exposes two clients:
| Client | Token | Used for |
|---|---|---|
DatasetQueryClient | PAT (pat_...) | create datasets, register/read schema, query |
DatasetIngestClient | project API key | push records to ingest |
const query = new DatasetQueryClient({
token: process.env.LOGURO_PAT!
});
const ingest = new DatasetIngestClient({
apiKey: process.env.LOGURO_PROJECT_API_KEY!
}); Create a dataset and register schema
const dataset = await query.createDataset({
projectSlug: 'local',
name: 'Orders',
description: 'Paid orders from billing'
});
await query.registerSchema({
projectSlug: 'local',
datasetId: dataset.id,
schema: Orders
}); registerSchema sends the schema contract expected by the dataset schema API.
Ingest records
await ingest.push(Orders, {
datasetId: dataset.id,
record: {
timestamp: new Date().toISOString(),
context: { source: 'billing-service', request_id: 'req_123' },
country: 'RO',
plan: 'pro',
amount: 42.5,
paid: true,
signup_at: '2026-06-01T08:00:00Z'
}
}); Batches use the same schema and record type:
await ingest.pushBatch(Orders, {
datasetId: dataset.id,
records: [
{
timestamp: '2026-06-10T11:00:00Z',
context: { source: 'billing-service' },
country: 'RO',
plan: 'pro',
amount: 20,
paid: true,
signup_at: '2026-06-01T08:00:00Z'
}
]
}); The SDK validates records before sending:
- required
timestamp - required object
context - declared fields are present
- field values match the schema type
- extra top-level fields are rejected
The ingest API still performs server-side validation. Client validation is there to catch mistakes earlier.
Query datasets
const result = await query.query(Orders, {
projectSlug: 'local',
datasetId: dataset.id,
range: {
from: '2026-06-01T00:00:00Z',
to: '2026-06-10T23:59:59Z'
},
select: [
Orders.field('country'),
Orders.count().as('events'),
Orders.sum('amount').as('revenue')
] as const,
where: [
Orders.where('plan', 'in', ['pro', 'team']),
Orders.where('amount', '>', 20),
Orders.where('paid', '=', true)
],
groupBy: ['country'],
orderBy: [{ field: 'revenue', direction: 'desc' }],
limit: 50
});
for (const row of result.rows) {
console.log(row.country, row.events, row.revenue);
} The query DSL maps to the JSON query API:
| SDK | API JSON |
|---|---|
Orders.field('country') | { "field": "country" } |
Orders.count().as('events') | { "aggregate": "count", "as": "events" } |
Orders.sum('amount').as('revenue') | { "aggregate": "sum", "field": "amount", "as": "revenue" } |
Orders.where('amount', '>', 20) | { "field": "amount", "op": ">", "value": 20 } |
Type safety
The SDK rejects incompatible expressions at compile time:
Orders.sum('amount'); // ok
Orders.sum('country'); // TypeScript error
Orders.where('paid', '=', true); // ok
Orders.where('paid', '=', 'true'); // TypeScript error
Orders.where('amount', 'contains', '4'); // TypeScript error Selected row types are inferred from the select array when it is marked as const:
const select = [
Orders.field('country'),
Orders.sum('amount').as('revenue')
] as const;
const result = await query.query(Orders, {
projectSlug: 'local',
datasetId: dataset.id,
select
});
// row.country is string
// row.revenue is number
for (const row of result.rows) {
console.log(row.country, row.revenue);
}