@abloatai/ablo 0.51.0 → 0.52.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.
@@ -2,32 +2,32 @@
2
2
  * Canonical cheap turn: explicitly attach the exact rows used to decide the
3
3
  * write. Ablo keeps their watermarks opaque and checks them at commit time.
4
4
  *
5
- * Run: ABLO_API_KEY=sk_... TASK_ID=task_... npx tsx examples/agent-turn.ts
5
+ * Run: ABLO_API_KEY=sk_... RECORD_ID=record_... npx tsx examples/agent-turn.ts
6
6
  */
7
7
  import { Ablo } from '@abloatai/ablo';
8
8
  import { defineSchema, model, z } from '@abloatai/ablo/schema';
9
9
 
10
10
  const schema = defineSchema({
11
- tasks: model({
11
+ records: model({
12
12
  title: z.string(),
13
13
  status: z.enum(['pending', 'done']),
14
14
  result: z.string().optional(),
15
15
  }),
16
16
  });
17
17
 
18
- const taskId = process.env.TASK_ID;
19
- if (!taskId) throw new Error('TASK_ID is required');
18
+ const recordId = process.env.RECORD_ID;
19
+ if (!recordId) throw new Error('RECORD_ID is required');
20
20
 
21
21
  const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
22
22
  try {
23
23
  await ablo.ready();
24
- const task = await ablo.tasks.get({ id: taskId });
25
- if (!task) throw new Error(`Task ${taskId} was not found`);
26
- const commitId = `task:${taskId}:cheap`;
27
- await ablo.tasks.update({
28
- id: task.id,
29
- data: { status: 'done', result: `Completed: ${task.title}` },
30
- reads: [task],
24
+ const record = await ablo.records.get({ id: recordId });
25
+ if (!record) throw new Error(`Record ${recordId} was not found`);
26
+ const commitId = `record:${recordId}:cheap`;
27
+ await ablo.records.update({
28
+ id: record.id,
29
+ data: { status: 'done', result: `Completed: ${record.title}` },
30
+ reads: [record],
31
31
  idempotencyKey: commitId,
32
32
  });
33
33
  const record = await ablo.commits.get({ id: commitId });
@@ -93,9 +93,9 @@ createServer(async (req, res) => {
93
93
  Replace the `Map`-based store in `customer-server.ts` with your real
94
94
  data layer. The handler shape stays the same:
95
95
 
96
- - `tasks.load({ id })` -> `db.task.findUnique({ where: { id } })`
97
- - `tasks.list({ query })` -> `db.task.findMany({ take, cursor })`
98
- - `tasks.commit({ operations, clientTxId })` -> `db.$transaction` that
96
+ - `records.load({ id })` -> `db.record.findUnique({ where: { id } })`
97
+ - `records.list({ query })` -> `db.record.findMany({ take, cursor })`
98
+ - `records.commit({ operations, clientTxId })` -> `db.$transaction` that
99
99
  applies each `op` and writes an outbox marker with `clientTxId` before commit
100
100
  - `events({ cursor, limit })` -> read from your outbox table, return
101
101
  rows with their `clientTxId` (Ablo dedupes its own commits) and the
@@ -23,7 +23,7 @@ import {
23
23
  } from '@abloatai/ablo/source';
24
24
  import { schema } from './schema';
25
25
 
26
- type TaskRow = {
26
+ type RecordRow = {
27
27
  id: string;
28
28
  title: string;
29
29
  status: 'todo' | 'doing' | 'done';
@@ -31,9 +31,9 @@ type TaskRow = {
31
31
  };
32
32
 
33
33
  // Stand-in for the customer's real database. Map keyed by row id.
34
- const taskStore = new Map<string, TaskRow>();
34
+ const recordStore = new Map<string, RecordRow>();
35
35
 
36
- // Outbox table. In production this is a `tasks_outbox` Postgres table
36
+ // Outbox table. In production this is a `records_outbox` Postgres table
37
37
  // populated in the same transaction as the app-row write. Ablo polls `events`
38
38
  // to fan out changes that bypassed Ablo, and to repair SDK-origin writes if
39
39
  // Ablo's immediate post-commit append failed.
@@ -41,8 +41,8 @@ const outbox: SourceEvent[] = [];
41
41
  let outboxSequence = 0;
42
42
 
43
43
  // Seed one row so the example's first `load` returns something.
44
- taskStore.set('task_seed', {
45
- id: 'task_seed',
44
+ recordStore.set('record_seed', {
45
+ id: 'record_seed',
46
46
  title: 'Seeded by customer database',
47
47
  status: 'todo',
48
48
  });
@@ -91,13 +91,13 @@ export const handleAbloSource = dataSource({
91
91
  return {};
92
92
  },
93
93
 
94
- tasks: {
94
+ records: {
95
95
  load({ id }) {
96
- return taskStore.get(id) ?? null;
96
+ return recordStore.get(id) ?? null;
97
97
  },
98
98
 
99
99
  list({ query }) {
100
- const all = Array.from(taskStore.values());
100
+ const all = Array.from(recordStore.values());
101
101
  const start = query.cursor ? Number(query.cursor) : 0;
102
102
  const limit = query.limit ?? 50;
103
103
  const page = all.slice(start, start + limit);
@@ -115,7 +115,7 @@ export const handleAbloSource = dataSource({
115
115
  // update; the surrounding `apply` helper shows where you would
116
116
  // open `db.transaction(async (tx) => { ... })`.
117
117
  commit({ operations, clientTxId }) {
118
- const rows: TaskRow[] = [];
118
+ const rows: RecordRow[] = [];
119
119
  for (const op of operations) {
120
120
  const row = applyOperation(op, clientTxId);
121
121
  if (row) rows.push(row);
@@ -145,38 +145,38 @@ export const handleAbloSource = dataSource({
145
145
  function applyOperation(
146
146
  op: SourceOperation,
147
147
  clientTxId: string | undefined,
148
- ): TaskRow | null {
149
- if (op.model !== 'tasks') return null;
150
- const id = op.id ?? `task_${Math.random().toString(36).slice(2, 10)}`;
148
+ ): RecordRow | null {
149
+ if (op.model !== 'records') return null;
150
+ const id = op.id ?? `record_${Math.random().toString(36).slice(2, 10)}`;
151
151
 
152
152
  if (op.type === 'CREATE') {
153
- const row: TaskRow = {
153
+ const row: RecordRow = {
154
154
  id,
155
155
  title: String(op.input?.title ?? ''),
156
156
  status:
157
- (op.input?.status as TaskRow['status'] | undefined) ?? 'todo',
157
+ (op.input?.status as RecordRow['status'] | undefined) ?? 'todo',
158
158
  ...(op.input?.assignee
159
159
  ? { assignee: String(op.input.assignee) }
160
160
  : {}),
161
161
  };
162
- taskStore.set(id, row);
162
+ recordStore.set(id, row);
163
163
  appendOutbox({ operation: op, entityId: id, data: row, clientTxId });
164
164
  return row;
165
165
  }
166
166
 
167
167
  if (op.type === 'UPDATE') {
168
- const existing = taskStore.get(id);
168
+ const existing = recordStore.get(id);
169
169
  if (!existing) return null;
170
- const next: TaskRow = { ...existing, ...(op.input as Partial<TaskRow>) };
171
- taskStore.set(id, next);
170
+ const next: RecordRow = { ...existing, ...(op.input as Partial<RecordRow>) };
171
+ recordStore.set(id, next);
172
172
  appendOutbox({ operation: op, entityId: id, data: next, clientTxId });
173
173
  return next;
174
174
  }
175
175
 
176
176
  if (op.type === 'DELETE') {
177
- const existing = taskStore.get(id);
177
+ const existing = recordStore.get(id);
178
178
  if (!existing) return null;
179
- taskStore.delete(id);
179
+ recordStore.delete(id);
180
180
  appendOutbox({ operation: op, entityId: id, data: null, clientTxId });
181
181
  return existing;
182
182
  }
@@ -187,7 +187,7 @@ function applyOperation(
187
187
  function appendOutbox(input: {
188
188
  operation: SourceOperation;
189
189
  entityId: string;
190
- data: TaskRow | null;
190
+ data: RecordRow | null;
191
191
  clientTxId: string | undefined;
192
192
  }): void {
193
193
  outboxSequence += 1;
@@ -205,11 +205,11 @@ function appendOutbox(input: {
205
205
  // Exposed for the orchestrator's `run.ts`. A real customer doesn't
206
206
  // need this — it's a back door for the demo to verify state.
207
207
  export function _inspectStore(): {
208
- rows: TaskRow[];
208
+ rows: RecordRow[];
209
209
  outboxSize: number;
210
210
  } {
211
211
  return {
212
- rows: Array.from(taskStore.values()),
212
+ rows: Array.from(recordStore.values()),
213
213
  outboxSize: outbox.length,
214
214
  };
215
215
  }
@@ -36,7 +36,7 @@ async function main() {
36
36
  });
37
37
 
38
38
  log('--- 1. load (existing seeded row) ---');
39
- const seeded = await driver.load('tasks', 'task_seed');
39
+ const seeded = await driver.load('records', 'record_seed');
40
40
  log('loaded:', seeded);
41
41
 
42
42
  log('\n--- 2. commit (CREATE + UPDATE in one batch) ---');
@@ -44,14 +44,14 @@ async function main() {
44
44
  [
45
45
  {
46
46
  type: 'CREATE',
47
- model: 'tasks',
48
- id: 'task_new',
47
+ model: 'records',
48
+ id: 'record_new',
49
49
  input: { title: 'Wire the data source', status: 'todo' },
50
50
  },
51
51
  {
52
52
  type: 'UPDATE',
53
- model: 'tasks',
54
- id: 'task_seed',
53
+ model: 'records',
54
+ id: 'record_seed',
55
55
  input: { status: 'doing', assignee: 'alice' },
56
56
  },
57
57
  ],
@@ -59,8 +59,8 @@ async function main() {
59
59
  );
60
60
  log('committed rows:', committed);
61
61
 
62
- log('\n--- 3. list (all tasks after commit) ---');
63
- const listed = await driver.list('tasks');
62
+ log('\n--- 3. list (all records after commit) ---');
63
+ const listed = await driver.list('records');
64
64
  log('listed:', listed);
65
65
 
66
66
  log('\n--- 4. events (outbox feed for cross-channel writes) ---');
@@ -73,7 +73,7 @@ async function main() {
73
73
  apiKey: 'sk_wrong_example_key',
74
74
  });
75
75
  try {
76
- await badDriver.load('tasks', 'task_seed');
76
+ await badDriver.load('records', 'record_seed');
77
77
  throw new Error('expected signature failure');
78
78
  } catch (err) {
79
79
  log('rejected as expected:', (err as Error).message);
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * The schema is the contract between three sides:
5
5
  *
6
- * 1. The application UI — `ablo.tasks.update(...)`.
6
+ * 1. The application UI — `ablo.records.update(...)`.
7
7
  * 2. The Ablo Cloud — translates writes into signed POSTs.
8
8
  * 3. The customer's Data Source endpoint — applies them to its own
9
9
  * database.
@@ -15,7 +15,7 @@
15
15
  import { defineSchema, model, z } from '@abloatai/ablo/schema';
16
16
 
17
17
  export const schema = defineSchema({
18
- tasks: model({
18
+ records: model({
19
19
  title: z.string(),
20
20
  status: z.enum(['todo', 'doing', 'done']),
21
21
  assignee: z.string().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/ablo",
3
- "version": "0.51.0",
3
+ "version": "0.52.0",
4
4
  "description": "The public Ablo SDK for coordinated reads, commits, claims, observation, and reactive applications.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -137,8 +137,8 @@
137
137
  "directory": "packages/ablo"
138
138
  },
139
139
  "dependencies": {
140
- "@abloatai/humans": "^0.51.0",
141
- "@abloatai/transaction": "^0.51.0",
140
+ "@abloatai/humans": "^0.52.0",
141
+ "@abloatai/transaction": "^0.52.0",
142
142
  "zod": "^4.4.3"
143
143
  },
144
144
  "peerDependencies": {