@abloatai/ablo 0.9.15 → 0.10.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Docs: add the 0.10.0 entry to the Version History & Migration Guide — the `test`/`live` → `sandbox`/`production` environment enum rename (key prefixes unchanged) and the new `transport: 'http'` stateless client.
8
+
9
+ ## 0.10.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Rename environment enum values to `production` and `sandbox` while preserving the existing `*_live_`/`*_test_` key prefix format.
14
+
15
+ ### Patch Changes
16
+
17
+ - Stateless HTTP transport for server-side actors, and a canonical environment vocabulary.
18
+ - **`Ablo({ transport: 'http' })`** returns a stateless `AbloHttpClient` for agents, workers, and serverless — the same `ablo.<model>` surface and coordination plane with no websocket: each call is one HTTP round-trip and identity rides the Bearer credential. The return type narrows so stateful-only APIs (`get`/`getAll`/`onChange`) are compile errors instead of latent runtime gaps.
19
+ - **Canonical `production` / `sandbox` environments** (new `environment.ts`, exported from the root): `sk_test_` / `sk_live_` remain the wire-level key prefixes but now map to `production` / `sandbox` everywhere — key parsing, source `mode`, and the CLI (which drops the legacy test/live config migration).
20
+ - **Source-mode commit scoping**: `commit` now forwards `projectId`, `accountScope`, and `environment` to customer storage resolvers, so per-project and sandbox/production traffic can be routed to distinct stores.
21
+ - **Fixes**: the WebSocket bearer credential is sent in the `ablo.bearer.<token>` subprotocol (never in the URL or proxy logs); `Model` no longer fabricates an `updatedAt` of "now" for records that arrive with only `createdAt`.
22
+
3
23
  ## 0.9.15
4
24
 
5
25
  ### Patch Changes
@@ -125,8 +125,9 @@ export interface UserContext {
125
125
  * `kind=agent` and the server applies capability-token auth. */
126
126
  kind?: 'user' | 'agent' | 'system';
127
127
  /** Restricted (`rk_`) API key for `kind: 'agent'` — the agent's
128
- * bearer credential. Sent as `?authorization=Bearer <token>` on the
129
- * WS upgrade. (Field name predates the Biscuit→opaque-key migration.) */
128
+ * bearer credential. Sent in the `ablo.bearer.<token>` WebSocket
129
+ * subprotocol, never in the URL. (Field name predates the
130
+ * Biscuit→opaque-key migration.) */
130
131
  capabilityToken?: string;
131
132
  /** Server-authoritative sync groups, supplied by auth/capability
132
133
  * exchange. The SDK does not invent org/user/default groups; app
package/dist/cli.cjs CHANGED
@@ -279453,13 +279453,12 @@ function asActiveProject(value) {
279453
279453
  return void 0;
279454
279454
  }
279455
279455
  function normalizeStoredMode(value) {
279456
- if (value === "sandbox" || value === "test") return "sandbox";
279457
- if (value === "production" || value === "live") return "production";
279456
+ if (value === "sandbox" || value === "production") return value;
279458
279457
  return void 0;
279459
279458
  }
279460
279459
  function extractEntries(obj) {
279461
- const sandbox = asKeyEntry(obj.sandbox) ?? asKeyEntry(obj.test);
279462
- const production = asKeyEntry(obj.production) ?? asKeyEntry(obj.live);
279460
+ const sandbox = asKeyEntry(obj.sandbox);
279461
+ const production = asKeyEntry(obj.production);
279463
279462
  if (sandbox || production) {
279464
279463
  return { ...sandbox ? { sandbox } : {}, ...production ? { production } : {} };
279465
279464
  }
@@ -29,6 +29,7 @@ import type { IntentStream, IntentWaitOptions, PresenceStream, Snapshot } from '
29
29
  import type { ParticipantManager } from '../sync/participants.js';
30
30
  import type { ActiveIntent, Duration, Intent, TargetRange } from '../types/streams.js';
31
31
  import { type AbloApi, type AbloApiClientOptions, type AbloApiIntents } from './ApiClient.js';
32
+ import { type AbloHttpClient, type AbloHttpClientOptions } from './httpClient.js';
32
33
  /**
33
34
  * Async function that resolves an apiKey at request time. Use for
34
35
  * credential rotation — rotate from a vault, refresh from session
@@ -125,6 +126,19 @@ export interface AbloOptions<S extends SchemaRecord = SchemaRecord> {
125
126
  * @default 'memory'
126
127
  */
127
128
  persistence?: AbloPersistence;
129
+ /**
130
+ * Transport selector. `'websocket'` (default) is the live client —
131
+ * persistent socket, local synced pool, `onChange` subscriptions. `'http'`
132
+ * returns the STATELESS client for server-side actors (agents, workers,
133
+ * serverless): same `ablo.<model>` surface and coordination plane, but each
134
+ * call is one HTTP round-trip, identity rides the Bearer credential, and no
135
+ * socket is ever opened. With `'http'` the return type narrows to
136
+ * `AbloHttpClient<S>`, so stateful-only capabilities (`get`/`getAll`,
137
+ * `onChange`) are compile errors rather than latent runtime gaps.
138
+ *
139
+ * @default 'websocket'
140
+ */
141
+ transport?: 'websocket' | 'http' | undefined;
128
142
  /**
129
143
  * Bearer auth token. Hosted-cloud consumers pass `apiKey`; self-hosted
130
144
  * deployments may pass a bearer token minted by their own auth layer.
@@ -944,7 +958,18 @@ export declare function computeFKDepthPriority(schema: Schema): ReadonlyMap<stri
944
958
  * const reports = sync.weatherReports.list({ where: { status: 'pending' } });
945
959
  * await sync.weatherReports.create({ location: 'Stockholm', status: 'pending' });
946
960
  * ```
961
+ *
962
+ * Pass `transport: 'http'` for the stateless server-side client (agents,
963
+ * workers, serverless) — same `ablo.<model>` surface, no socket:
964
+ *
965
+ * ```ts
966
+ * const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY, transport: 'http' });
967
+ * await ablo.tasks.update({ id, data: { status: 'done' } });
968
+ * ```
947
969
  */
970
+ export declare function Ablo<const S extends SchemaRecord>(options: AbloHttpClientOptions<S> & {
971
+ transport: 'http';
972
+ }): AbloHttpClient<S>;
948
973
  export declare function Ablo<const S extends SchemaRecord>(options: AbloOptions<S>): Ablo<S>;
949
974
  export declare function Ablo(options: AbloApiClientOptions): AbloApi;
950
975
  import type * as _Streams from '../types/streams.js';
@@ -37,6 +37,9 @@ import { awaitIntentGrant } from '../sync/awaitIntentGrant.js';
37
37
  import { createSnapshot } from '../sync/createSnapshot.js';
38
38
  import { createParticipantManager } from '../sync/participants.js';
39
39
  import { createProtocolClient, } from './ApiClient.js';
40
+ // Value import is cycle-safe: httpClient.js only value-imports ApiClient.js,
41
+ // which imports this module type-only.
42
+ import { createAbloHttpClient, } from './httpClient.js';
40
43
  import { assertBrowserSafety, readProcessEnv, resolveApiKey, resolveApiKeyValue, resolveAuthToken, resolveBaseURL, resolveBootstrapBaseUrl, resolveDatabaseUrl, } from './auth.js';
41
44
  import { registerDataSource } from './registerDataSource.js';
42
45
  import { shouldUseInMemoryPersistence, } from './persistence.js';
@@ -684,8 +687,13 @@ function resolveCredentialResolver(options) {
684
687
  }
685
688
  export function Ablo(options) {
686
689
  if (options.schema == null) {
690
+ // The protocol client IS the stateless HTTP plane (string-keyed models),
691
+ // so `transport: 'http'` needs no special-casing here.
687
692
  return createProtocolClient(options);
688
693
  }
694
+ if (options.transport === 'http') {
695
+ return createAbloHttpClient(options);
696
+ }
689
697
  const internalOptions = options;
690
698
  const env = readProcessEnv();
691
699
  const authInput = { options, env };
@@ -124,7 +124,7 @@ export type WarningCode = 'drop_model' | 'drop_field' | 'risky_cast' | 'lossy_re
124
124
  /** A model disappears from what this plane's READERS resolve, without any
125
125
  * table being dropped. Emitted by the server's push gate (not
126
126
  * `classifyMigration`) when a first sandbox push shadows the production
127
- * artifact that sandbox readers were served via the registry's testlive
127
+ * artifact that sandbox readers were served via the registry's sandboxproduction
128
128
  * fallback. The data plane is untouched — the loss is visibility. */
129
129
  | 'remove_model';
130
130
  export type BlockerCode = 'required_field_added' | 'made_required';
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import type { ParticipantKind, ConfirmationState } from '../schema/sync-delta-row.js';
17
17
  import type { ParticipantRef } from '../schema/sync-delta-wire.js';
18
+ import type { Environment } from '../environment.js';
18
19
  export interface CommitContext {
19
20
  participantId: string;
20
21
  /**
@@ -24,6 +25,19 @@ export interface CommitContext {
24
25
  */
25
26
  participantKind: ParticipantKind;
26
27
  organizationId: string;
28
+ /**
29
+ * Product/project scope for routing source-mode storage. Omitted means the
30
+ * org-default project (the legacy behavior).
31
+ */
32
+ projectId?: string;
33
+ /** Optional external account scope forwarded to storage resolvers. */
34
+ accountScope?: string;
35
+ /**
36
+ * Canonical environment for this commit. Source-mode adapters forward this to
37
+ * customer handlers so sandbox and production traffic can hit distinct
38
+ * customer-owned stores.
39
+ */
40
+ environment?: Environment;
27
41
  /**
28
42
  * The participant's own subscribed sync groups (from the WS upgrade or
29
43
  * capability token). Appended to every delta's `sync_groups` so writes fan
@@ -1,4 +1,5 @@
1
1
  import type { Schema, SchemaRecord, InferCreate } from '../schema/schema.js';
2
+ import type { Environment } from '../environment.js';
2
3
  import type { DataSourceAdapter } from './adapter.js';
3
4
  export type SourcePrimitive = string | number | boolean | null;
4
5
  export type SourceWhere = readonly [field: string, value: SourcePrimitive] | readonly [
@@ -47,21 +48,21 @@ export interface SourceRequestContext {
47
48
  readonly organizationId?: string;
48
49
  readonly requiredSyncGroups?: readonly string[];
49
50
  /**
50
- * Test/live mode for this request. Customers branch their source
51
- * handlers on this (`if (mode === 'test') db = testDb`) so test
51
+ * Production/sandbox mode for this request. Customers branch their source
52
+ * handlers on this (`if (mode === 'sandbox') db = sandboxDb`) so sandbox
52
53
  * traffic exercises the same code path against an isolated store.
53
54
  *
54
- * Mirrors Stripe's `sk_test_` / `sk_live_` distinction: same wire
55
+ * Mirrors Stripe's `sk_test_` / `sk_live_` prefixes: same wire
55
56
  * shape, same handler code, different namespace. Ablo's server-side
56
57
  * fan-out does not yet partition deltas by mode — that lands when
57
58
  * `sync_deltas.mode` ships. Until then, isolation is enforced
58
59
  * customer-side via this field, which is the right boundary anyway
59
60
  * (the customer's database is where the canonical data lives).
60
61
  *
61
- * Defaults to `'live'` when omitted so callers that don't opt in
62
+ * Defaults to `'production'` when omitted so callers that don't opt in
62
63
  * keep the existing behavior.
63
64
  */
64
- readonly mode?: 'test' | 'live';
65
+ readonly mode?: Environment;
65
66
  }
66
67
  export interface SourceOperation {
67
68
  readonly type: 'CREATE' | 'UPDATE' | 'DELETE' | 'ARCHIVE' | 'UNARCHIVE';
@@ -92,11 +92,10 @@ export interface SyncWebSocketOptions {
92
92
  kind?: 'user' | 'agent' | 'system';
93
93
  /**
94
94
  * The agent's bearer credential — a restricted (`rk_`) API key. When
95
- * set, sent as `?authorization=Bearer+<token>` on the WS upgrade —
96
- * query-param form so it works in both Node (no header support) and
97
- * browsers. The server's auth path accepts either form. Required for
98
- * `kind: 'agent'`; ignored for `kind: 'user'`. (Field name predates
99
- * the Biscuit→opaque-key migration.)
95
+ * set, sent in the `ablo.bearer.<token>` WebSocket subprotocol so the
96
+ * credential stays out of URLs and proxy logs. Required for `kind: 'agent'`;
97
+ * ignored for `kind: 'user'`. (Field name predates the Biscuit→opaque-key
98
+ * migration.)
100
99
  */
101
100
  capabilityToken?: string;
102
101
  /**
package/docs/migration.md CHANGED
@@ -11,6 +11,7 @@ change when you upgrade.
11
11
 
12
12
  | Version | What changed | What to do |
13
13
  |---|---|---|
14
+ | **0.10.0** | Environment enum renamed `test`/`live` → `sandbox`/`production` | Update code that branches on the environment (e.g. source `mode`): `'test'`→`'sandbox'`, `'live'`→`'production'`. Key prefixes `sk_test_`/`sk_live_` are unchanged |
14
15
  | **0.9.2** | `turn` primitive + agent-work `tasks` resource removed | Coordinate with `claim`; mint a scoped session instead of `agent().run()` |
15
16
  | **0.9.2** | `intents` deprecated in favor of `claim` | Use `ablo.<model>.claim`; `ablo.intents` is now `@internal` |
16
17
  | **0.9.0** | One options object per verb | `update(id, data, opts)` → `update({ id, data, ...opts })` |
@@ -23,6 +24,57 @@ change when you upgrade.
23
24
 
24
25
  ---
25
26
 
27
+ ## 0.10.0 — environment enum `sandbox` / `production`; stateless HTTP transport
28
+
29
+ ### Environment enum rename (the only breaking change)
30
+
31
+ The canonical environment values are now **`production`** and **`sandbox`** (was
32
+ `live` and `test`). This is a *vocabulary* change at the type/API layer — the
33
+ on-the-wire key prefixes are **unchanged**: keys are still `sk_test_…` /
34
+ `sk_live_…` and parse exactly as before. What changed is the enum you see in
35
+ code: `Environment`, the source-handler `mode` field, and `ApiKeyEnv` now read
36
+ `production` / `sandbox`.
37
+
38
+ You only need to act if your code branches on the environment value — most
39
+ commonly a Data Source handler keyed on `mode`. The mapping is exactly
40
+ `test → sandbox`, `live → production`:
41
+
42
+ ```diff
43
+ const handler = createSourceHandler({
44
+ read: async ({ mode }) => {
45
+ - const db = mode === 'test' ? testDb : liveDb;
46
+ + const db = mode === 'sandbox' ? sandboxDb : productionDb;
47
+ // …
48
+ },
49
+ });
50
+ ```
51
+
52
+ `commit` now also forwards `projectId`, `accountScope`, and `environment` to
53
+ source resolvers, so per-project and per-environment traffic can be routed to
54
+ distinct stores.
55
+
56
+ > **CLI note:** the legacy single-file config that stored `test`/`live` key
57
+ > buckets is no longer auto-migrated. If `ablo status` can't find your keys after
58
+ > upgrading, re-run `ablo login` to write the current `sandbox`/`production`
59
+ > layout.
60
+
61
+ ### New (non-breaking): `transport: 'http'`
62
+
63
+ `Ablo({ transport: 'http' })` returns a stateless `AbloHttpClient` for
64
+ server-side actors (agents, workers, serverless): the same `ablo.<model>` surface
65
+ and `claim` coordination, but each call is one HTTP round-trip with identity on
66
+ the Bearer credential — no websocket, no local synced pool. The return type
67
+ narrows, so stateful-only APIs (`get` / `getAll` / `onChange`) become compile
68
+ errors instead of latent runtime gaps. Existing code keeps the default
69
+ `'websocket'` transport, unchanged.
70
+
71
+ ```ts
72
+ const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY, transport: 'http' });
73
+ await ablo.tasks.update({ id, data: { status: 'done' } });
74
+ ```
75
+
76
+ ---
77
+
26
78
  ## 0.9.2 — `turn` / agent-`tasks` removed; `intents` deprecated
27
79
 
28
80
  The SDK's coordination surface is now exactly two things: `ablo.<model>` writes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/ablo",
3
- "version": "0.9.15",
3
+ "version": "0.10.1",
4
4
  "description": "The Collaboration Layer For AI Agents",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",