@abloatai/ablo 0.8.0 → 0.9.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 +46 -1
- package/README.md +33 -28
- package/dist/BaseSyncedStore.d.ts +83 -0
- package/dist/BaseSyncedStore.js +194 -2
- package/dist/Model.d.ts +42 -0
- package/dist/Model.js +103 -44
- package/dist/agent/session.js +3 -3
- package/dist/ai-sdk/coordination-context.js +4 -0
- package/dist/ai-sdk/index.d.ts +56 -47
- package/dist/ai-sdk/index.js +56 -47
- package/dist/ai-sdk/intent-broadcast.d.ts +5 -0
- package/dist/ai-sdk/intent-broadcast.js +11 -4
- package/dist/ai-sdk/wrap.d.ts +14 -11
- package/dist/ai-sdk/wrap.js +11 -13
- package/dist/auth/credentialSource.d.ts +34 -0
- package/dist/auth/credentialSource.js +63 -0
- package/dist/auth/index.d.ts +2 -22
- package/dist/auth/index.js +4 -42
- package/dist/auth/schemas.d.ts +35 -0
- package/dist/auth/schemas.js +53 -0
- package/dist/client/Ablo.d.ts +160 -42
- package/dist/client/Ablo.js +145 -75
- package/dist/client/ApiClient.d.ts +20 -4
- package/dist/client/ApiClient.js +166 -28
- package/dist/client/auth.d.ts +14 -5
- package/dist/client/auth.js +60 -7
- package/dist/client/createInternalComponents.d.ts +2 -0
- package/dist/client/createInternalComponents.js +8 -1
- package/dist/client/createModelProxy.d.ts +130 -66
- package/dist/client/createModelProxy.js +152 -49
- package/dist/client/httpClient.d.ts +71 -0
- package/dist/client/httpClient.js +69 -0
- package/dist/client/identity.d.ts +2 -6
- package/dist/client/identity.js +49 -11
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/registerDataSource.d.ts +3 -3
- package/dist/client/registerDataSource.js +11 -9
- package/dist/client/validateAbloOptions.js +1 -1
- package/dist/core/DatabaseManager.js +30 -2
- package/dist/core/openIDBWithTimeout.d.ts +36 -0
- package/dist/core/openIDBWithTimeout.js +88 -1
- package/dist/errorCodes.d.ts +70 -1
- package/dist/errorCodes.js +108 -9
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +72 -22
- package/dist/index.d.ts +17 -8
- package/dist/index.js +15 -6
- package/dist/keys/index.d.ts +16 -1
- package/dist/keys/index.js +26 -6
- package/dist/mutators/UndoManager.d.ts +158 -50
- package/dist/mutators/UndoManager.js +345 -22
- package/dist/mutators/inverseOp.d.ts +129 -0
- package/dist/mutators/inverseOp.js +74 -0
- package/dist/mutators/readerActions.d.ts +1 -1
- package/dist/mutators/undoApply.d.ts +42 -0
- package/dist/mutators/undoApply.js +143 -0
- package/dist/query/client.d.ts +10 -9
- package/dist/query/client.js +3 -6
- package/dist/react/AbloProvider.d.ts +23 -126
- package/dist/react/AbloProvider.js +62 -199
- package/dist/react/context.d.ts +31 -0
- package/dist/react/useAblo.d.ts +2 -2
- package/dist/react/useCurrentUserId.d.ts +1 -1
- package/dist/react/useCurrentUserId.js +1 -1
- package/dist/react/useMutators.js +19 -12
- package/dist/schema/ddl.d.ts +34 -3
- package/dist/schema/ddl.js +162 -4
- package/dist/schema/index.d.ts +5 -1
- package/dist/schema/index.js +13 -1
- package/dist/schema/model.d.ts +11 -0
- package/dist/schema/model.js +2 -0
- package/dist/schema/openapi.d.ts +28 -0
- package/dist/schema/openapi.js +118 -0
- package/dist/schema/plane.d.ts +23 -0
- package/dist/schema/plane.js +19 -0
- package/dist/schema/relation.d.ts +20 -0
- package/dist/schema/serialize.d.ts +4 -0
- package/dist/schema/serialize.js +4 -0
- package/dist/schema/sync-delta-row.d.ts +157 -0
- package/dist/schema/sync-delta-row.js +102 -0
- package/dist/schema/sync-delta-wire.d.ts +180 -0
- package/dist/schema/sync-delta-wire.js +102 -0
- package/dist/server/adapter.d.ts +156 -0
- package/dist/server/adapter.js +19 -0
- package/dist/server/commit.d.ts +82 -0
- package/dist/server/commit.js +1 -0
- package/dist/server/index.d.ts +14 -0
- package/dist/server/index.js +1 -0
- package/dist/server/next.d.ts +51 -0
- package/dist/server/next.js +47 -0
- package/dist/server/read-config.d.ts +60 -0
- package/dist/server/read-config.js +8 -0
- package/dist/server/storage-mode.d.ts +17 -0
- package/dist/server/storage-mode.js +12 -0
- package/dist/source/adapter.d.ts +65 -0
- package/dist/source/adapter.js +20 -0
- package/dist/source/adapters/drizzle.d.ts +43 -0
- package/dist/source/adapters/drizzle.js +185 -0
- package/dist/source/adapters/memory.d.ts +12 -0
- package/dist/source/adapters/memory.js +114 -0
- package/dist/source/adapters/prisma.d.ts +57 -0
- package/dist/source/adapters/prisma.js +176 -0
- package/dist/source/conformance.d.ts +32 -0
- package/dist/source/conformance.js +134 -0
- package/dist/source/contract.d.ts +144 -0
- package/dist/source/contract.js +99 -0
- package/dist/source/index.d.ts +62 -10
- package/dist/source/index.js +99 -0
- package/dist/source/migrations.d.ts +14 -0
- package/dist/source/migrations.js +39 -0
- package/dist/source/next.d.ts +33 -0
- package/dist/source/next.js +26 -0
- package/dist/sync/BootstrapHelper.d.ts +10 -0
- package/dist/sync/BootstrapHelper.js +10 -15
- package/dist/sync/ConnectionManager.d.ts +55 -1
- package/dist/sync/ConnectionManager.js +155 -16
- package/dist/sync/HydrationCoordinator.d.ts +93 -17
- package/dist/sync/HydrationCoordinator.js +238 -39
- package/dist/sync/NetworkProbe.d.ts +58 -24
- package/dist/sync/NetworkProbe.js +118 -42
- package/dist/sync/SyncWebSocket.d.ts +45 -70
- package/dist/sync/SyncWebSocket.js +70 -36
- package/dist/sync/createIntentStream.js +10 -1
- package/dist/types/streams.d.ts +9 -0
- package/dist/utils/mobx-setup.js +1 -0
- package/dist/webhooks/events.d.ts +38 -0
- package/dist/webhooks/events.js +40 -0
- package/dist/webhooks/index.d.ts +10 -0
- package/dist/webhooks/index.js +10 -0
- package/dist/wire/errorEnvelope.d.ts +34 -0
- package/dist/wire/errorEnvelope.js +86 -0
- package/dist/wire/frames.d.ts +119 -0
- package/dist/wire/frames.js +1 -0
- package/dist/wire/index.d.ts +24 -0
- package/dist/wire/index.js +21 -0
- package/dist/wire/listEnvelope.d.ts +45 -0
- package/dist/wire/listEnvelope.js +17 -0
- package/docs/api.md +47 -44
- package/docs/cli.md +44 -44
- package/docs/client-behavior.md +30 -30
- package/docs/coordination.md +33 -36
- package/docs/data-sources.md +35 -15
- package/docs/examples/agent-human.md +45 -43
- package/docs/examples/ai-sdk-tool.md +20 -16
- package/docs/examples/existing-python-backend.md +16 -12
- package/docs/examples/nextjs.md +14 -12
- package/docs/examples/scoped-agent.md +1 -1
- package/docs/examples/server-agent.md +24 -21
- package/docs/guarantees.md +15 -13
- package/docs/index.md +2 -2
- package/docs/integration-guide.md +30 -30
- package/docs/interaction-model.md +19 -23
- package/docs/mcp/claude-code.md +3 -3
- package/docs/mcp/cursor.md +1 -1
- package/docs/mcp/windsurf.md +2 -2
- package/docs/mcp.md +6 -6
- package/docs/quickstart.md +41 -31
- package/docs/react.md +13 -9
- package/docs/schema-contract.md +12 -10
- package/docs/the-loop.md +21 -0
- package/examples/data-source/README.md +4 -5
- package/examples/data-source/customer-server.ts +27 -25
- package/llms.txt +28 -5
- package/package.json +43 -3
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@abloatai/ablo/server` — the DataAdapter CONTRACT vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* This is the Better-Auth-style seam: the package defines the storage
|
|
5
|
+
* interface and its value types; a host (today `apps/sync-server`, tomorrow a
|
|
6
|
+
* consumer's app) implements it against a real database. The reference Postgres
|
|
7
|
+
* implementation (`executeCommit` + `selectAdapter`) stays host-side — it
|
|
8
|
+
* carries a `postgres` driver and raw SQL, which must never enter this
|
|
9
|
+
* browser-shippable package.
|
|
10
|
+
*
|
|
11
|
+
* Scope note: this file holds the parts of the contract that are PURE — they
|
|
12
|
+
* depend only on primitives and {@link Row}. The full `DataAdapter` interface
|
|
13
|
+
* (and its `sync()` method) references `SyncDelta`, which currently has two
|
|
14
|
+
* definitions (server `db/deltas` vs package `core`) pending unification; the
|
|
15
|
+
* `commit`/`read` request envelopes reference server domain types
|
|
16
|
+
* (`CommitContext`, `BootstrapModel`). Those stay server-local and re-import
|
|
17
|
+
* these primitives until that canonicalization lands.
|
|
18
|
+
*/
|
|
19
|
+
import type { SourceListQuery, SourceOperation, SourceRequestContext } from '../source/index.js';
|
|
20
|
+
import type { ServerSyncDelta } from '../schema/sync-delta-wire.js';
|
|
21
|
+
import type { BootstrapModel } from './read-config.js';
|
|
22
|
+
import type { CommitContext, CommitResult } from './commit.js';
|
|
23
|
+
import type { StorageMode } from './storage-mode.js';
|
|
24
|
+
/**
|
|
25
|
+
* A canonical row — one model record keyed by column name. The VALUE type is
|
|
26
|
+
* `unknown`, on purpose and as a best practice: a row's columns are JSONB /
|
|
27
|
+
* driver-dynamic, so `unknown` forces callers to narrow before use (the safe
|
|
28
|
+
* opposite of `any`). This is the single named domain type for "a row"; nothing
|
|
29
|
+
* in the spine uses a bare `unknown[]`. When the schema engine emits per-model
|
|
30
|
+
* types, `read<T>()` can narrow this to `T` without touching call sites.
|
|
31
|
+
*/
|
|
32
|
+
export type Row = Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* The result of a {@link Row} read. Two shapes mirror the two request kinds:
|
|
35
|
+
* - `bootstrap`: full-load — model name → its rows (empty models omitted).
|
|
36
|
+
* - `query`: a single filtered model query.
|
|
37
|
+
*/
|
|
38
|
+
export type ReadResult = {
|
|
39
|
+
readonly kind: 'bootstrap';
|
|
40
|
+
/** model name → its rows. Empty models are omitted. */
|
|
41
|
+
readonly models: Record<string, Row[]>;
|
|
42
|
+
/** Models whose read failed (partial success), if any. */
|
|
43
|
+
readonly failedModels?: string[];
|
|
44
|
+
} | {
|
|
45
|
+
readonly kind: 'query';
|
|
46
|
+
readonly rows: readonly Row[];
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Resume position for `sync` — the client's last-seen `sync_deltas` watermark.
|
|
50
|
+
* Deliberately JUST the position: org / syncGroups / maxGap are server-derived
|
|
51
|
+
* and bound onto the adapter at resolve time (a trust boundary — the client
|
|
52
|
+
* never supplies the org it reads). `lastSyncId <= 0` means "no position yet",
|
|
53
|
+
* which `sync` reports as `needsFullRead`.
|
|
54
|
+
*/
|
|
55
|
+
export interface SyncCursor {
|
|
56
|
+
readonly lastSyncId: number;
|
|
57
|
+
}
|
|
58
|
+
export interface DataAdapterCapabilities {
|
|
59
|
+
/** The backend can dry-run a change without committing it. */
|
|
60
|
+
readonly propose?: boolean;
|
|
61
|
+
/** `commit` is atomic (all-or-nothing) across the change's operations. */
|
|
62
|
+
readonly transactions?: boolean;
|
|
63
|
+
/** Changes fan out in real time (vs poll-only). */
|
|
64
|
+
readonly realtime?: boolean;
|
|
65
|
+
/** The backend can be introspected for its schema. */
|
|
66
|
+
readonly schemaIntrospection?: boolean;
|
|
67
|
+
}
|
|
68
|
+
/** Result of a dry-run proposal (only adapters with `capabilities.propose`). */
|
|
69
|
+
export interface ProposalResult {
|
|
70
|
+
readonly ok: boolean;
|
|
71
|
+
readonly conflicts?: readonly {
|
|
72
|
+
readonly model: string;
|
|
73
|
+
readonly id: string;
|
|
74
|
+
readonly reason: string;
|
|
75
|
+
}[];
|
|
76
|
+
readonly rows?: readonly Row[];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A request for canonical rows. Two shapes:
|
|
80
|
+
* - `bootstrap`: the full-load reader — give me every (enabled) model's rows.
|
|
81
|
+
* - `query`: a single filtered model query (the live `/sync/query` path).
|
|
82
|
+
*
|
|
83
|
+
* The `query` shape keeps the hosted SQL/tenant/RLS execution as a `runHosted`
|
|
84
|
+
* closure (same seam as a commit's `runHosted`): the adapter DISPATCHES — source
|
|
85
|
+
* → the customer's `list`, hosted/selfHosted → run the closure — but the query
|
|
86
|
+
* engine itself stays host-side rather than leaking into the adapter.
|
|
87
|
+
*/
|
|
88
|
+
export type ReadRequest = {
|
|
89
|
+
readonly kind: 'bootstrap';
|
|
90
|
+
readonly models: readonly BootstrapModel[];
|
|
91
|
+
readonly requestedModels?: readonly string[];
|
|
92
|
+
readonly scope?: SourceRequestContext;
|
|
93
|
+
} | {
|
|
94
|
+
readonly kind: 'query';
|
|
95
|
+
readonly model: string;
|
|
96
|
+
/** Source-side model name, when it differs from `model`. */
|
|
97
|
+
readonly sourceModel?: string;
|
|
98
|
+
/** `__typename` stamped on each returned row. */
|
|
99
|
+
readonly typename: string;
|
|
100
|
+
readonly query: SourceListQuery;
|
|
101
|
+
readonly scope?: SourceRequestContext;
|
|
102
|
+
/** Hosted/self-hosted execution (compile + tenant pool + RLS + unpack). */
|
|
103
|
+
readonly runHosted: () => Promise<Row[]>;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* A change to apply to the canonical store. `runHosted` is the pre-bound local
|
|
107
|
+
* mutator execution (the hosted/self-hosted write path lives in the mutator
|
|
108
|
+
* engine above the adapter); the hosted adapter simply runs it, while the source
|
|
109
|
+
* adapter ignores it and ships the operations to the customer endpoint. This is
|
|
110
|
+
* the seam that lets the adapter OWN the mode decision while the heavy mutator
|
|
111
|
+
* logic stays host-side.
|
|
112
|
+
*/
|
|
113
|
+
export interface ChangeSet {
|
|
114
|
+
readonly operations: readonly SourceOperation[];
|
|
115
|
+
readonly context: CommitContext;
|
|
116
|
+
readonly clientTxId: string;
|
|
117
|
+
readonly runHosted: () => Promise<CommitResult>;
|
|
118
|
+
}
|
|
119
|
+
export interface SyncResult {
|
|
120
|
+
/** Deltas in `(cursor.lastSyncId, nextCursor.lastSyncId]`, scoped to syncGroups. */
|
|
121
|
+
readonly changes: readonly ServerSyncDelta[];
|
|
122
|
+
readonly nextCursor: {
|
|
123
|
+
readonly lastSyncId: number;
|
|
124
|
+
};
|
|
125
|
+
/** True when the gap was too large to stream — caller must full-`read`. */
|
|
126
|
+
readonly needsFullRead: boolean;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The ONE interface every storage mode implements — the Better-Auth-style
|
|
130
|
+
* `Adapter` seam. The package owns this contract; a host (today
|
|
131
|
+
* `apps/sync-server`, tomorrow a consumer app) implements it against a real
|
|
132
|
+
* database. Design rule (load-bearing, = Zero's mutator principle): **adapters
|
|
133
|
+
* only guarantee reality access.** `read` fetches canonical rows, `commit`
|
|
134
|
+
* applies a change, `sync` reads the change log; orchestration (proposal,
|
|
135
|
+
* conflict policy) lives ABOVE the adapter. `propose` is a capability, never a
|
|
136
|
+
* required method.
|
|
137
|
+
*/
|
|
138
|
+
export interface DataAdapter {
|
|
139
|
+
/** Diagnostic discriminator (≈ Better Auth's `adapterId`). Routing decisions
|
|
140
|
+
* go through the resolver/factory, not this. */
|
|
141
|
+
readonly mode: StorageMode;
|
|
142
|
+
readonly capabilities: DataAdapterCapabilities;
|
|
143
|
+
read(req: ReadRequest): Promise<ReadResult>;
|
|
144
|
+
commit(change: ChangeSet): Promise<CommitResult>;
|
|
145
|
+
sync(cursor: SyncCursor): Promise<SyncResult>;
|
|
146
|
+
}
|
|
147
|
+
/** An adapter whose backend can dry-run. Narrow to this only after checking the capability. */
|
|
148
|
+
export interface ProposableDataAdapter extends DataAdapter {
|
|
149
|
+
propose(change: ChangeSet): Promise<ProposalResult>;
|
|
150
|
+
}
|
|
151
|
+
/** Resolves an authenticated scope to the adapter that serves it (≈ Better Auth's
|
|
152
|
+
* `createAdapter` factory seam). */
|
|
153
|
+
export type AdapterResolver = (scope: {
|
|
154
|
+
readonly projectId: string;
|
|
155
|
+
readonly accountScope?: string;
|
|
156
|
+
}) => Promise<DataAdapter> | DataAdapter;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@abloatai/ablo/server` — the DataAdapter CONTRACT vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* This is the Better-Auth-style seam: the package defines the storage
|
|
5
|
+
* interface and its value types; a host (today `apps/sync-server`, tomorrow a
|
|
6
|
+
* consumer's app) implements it against a real database. The reference Postgres
|
|
7
|
+
* implementation (`executeCommit` + `selectAdapter`) stays host-side — it
|
|
8
|
+
* carries a `postgres` driver and raw SQL, which must never enter this
|
|
9
|
+
* browser-shippable package.
|
|
10
|
+
*
|
|
11
|
+
* Scope note: this file holds the parts of the contract that are PURE — they
|
|
12
|
+
* depend only on primitives and {@link Row}. The full `DataAdapter` interface
|
|
13
|
+
* (and its `sync()` method) references `SyncDelta`, which currently has two
|
|
14
|
+
* definitions (server `db/deltas` vs package `core`) pending unification; the
|
|
15
|
+
* `commit`/`read` request envelopes reference server domain types
|
|
16
|
+
* (`CommitContext`, `BootstrapModel`). Those stay server-local and re-import
|
|
17
|
+
* these primitives until that canonicalization lands.
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@abloatai/ablo/server` — the COMMIT contract types.
|
|
3
|
+
*
|
|
4
|
+
* `CommitContext` is the attribution/intent envelope the host's commit executor
|
|
5
|
+
* stamps onto every delta a batch produces; `CommitResult` is the receipt. Both
|
|
6
|
+
* are PURE descriptors (no `postgres`, no SQL, no functions), which is why they
|
|
7
|
+
* live in the portable package while the SQL engine that consumes them
|
|
8
|
+
* (`executeCommit`) stays host-side. They feed the `ChangeSet`/`DataAdapter`
|
|
9
|
+
* contract.
|
|
10
|
+
*
|
|
11
|
+
* The attribution fields reuse the canonical `ParticipantKind` /
|
|
12
|
+
* `ConfirmationState` / `ParticipantRef` so the commit-time shape and the
|
|
13
|
+
* stored/broadcast delta shape share ONE source of truth (these were previously
|
|
14
|
+
* a server-local interface "kept in sync by convention").
|
|
15
|
+
*/
|
|
16
|
+
import type { ParticipantKind, ConfirmationState } from '../schema/sync-delta-row.js';
|
|
17
|
+
import type { ParticipantRef } from '../schema/sync-delta-wire.js';
|
|
18
|
+
export interface CommitContext {
|
|
19
|
+
participantId: string;
|
|
20
|
+
/**
|
|
21
|
+
* Typed participant classification — required so every delta written through
|
|
22
|
+
* the commit executor carries structured attribution, not a string-prefix
|
|
23
|
+
* convention.
|
|
24
|
+
*/
|
|
25
|
+
participantKind: ParticipantKind;
|
|
26
|
+
organizationId: string;
|
|
27
|
+
/**
|
|
28
|
+
* The participant's own subscribed sync groups (from the WS upgrade or
|
|
29
|
+
* capability token). Appended to every delta's `sync_groups` so writes fan
|
|
30
|
+
* out to agents scoped to entity-level groups (e.g. `deck:abc`), not only the
|
|
31
|
+
* default `org:X` / `user:Y` surface. Callers that omit this fall back to the
|
|
32
|
+
* legacy `[org, user]` broadcast behavior.
|
|
33
|
+
*/
|
|
34
|
+
syncGroups?: readonly string[];
|
|
35
|
+
/**
|
|
36
|
+
* Sandbox keys should not stamp `org:<organizationId>` on deltas — otherwise
|
|
37
|
+
* live org subscribers would see test-environment writes.
|
|
38
|
+
*/
|
|
39
|
+
omitOrgSyncGroup?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* On-behalf-of attribution — whose authority the actor acted under. For
|
|
42
|
+
* human-direct commits, equals the actor. For agent commits, the human at the
|
|
43
|
+
* root of the capability's delegation chain. Null for `system` principals.
|
|
44
|
+
*/
|
|
45
|
+
onBehalfOf?: ParticipantRef | null;
|
|
46
|
+
/**
|
|
47
|
+
* FK to AgentCapabilityRoot.capabilityId. Non-null for agent / system commits
|
|
48
|
+
* authorized by a Biscuit; null for human-direct commits. Embedded in every
|
|
49
|
+
* delta so the audit chain "delta → capability → human" is one FK hop.
|
|
50
|
+
*/
|
|
51
|
+
capabilityId?: string | null;
|
|
52
|
+
/**
|
|
53
|
+
* ApiKey row id when the caller authenticated with an API key. Used by the
|
|
54
|
+
* idempotency cache and usage attribution. Null for session / capability
|
|
55
|
+
* callers.
|
|
56
|
+
*/
|
|
57
|
+
apiKeyId?: string | null;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the human explicitly approved the change. Defaults to `auto` until
|
|
60
|
+
* the chat-side previewed/approved plumbing lands.
|
|
61
|
+
*/
|
|
62
|
+
confirmationState?: ConfirmationState;
|
|
63
|
+
/**
|
|
64
|
+
* FK to AgentTurn.id. Pinned at turn open by `SyncAgent.beginTurn`, threaded
|
|
65
|
+
* through the wire frame's `causedByTaskId`, validated by the commit handler
|
|
66
|
+
* (turn must belong to the same agent and be open), and written onto every
|
|
67
|
+
* delta this batch produces. Absent for human-direct commits and SDKs that
|
|
68
|
+
* predate the turn protocol (→ `caused_by_task_id = NULL`).
|
|
69
|
+
*/
|
|
70
|
+
causedByTaskId?: string | null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The receipt of a commit. Pins the exact `sync_deltas` id range the batch
|
|
74
|
+
* produced, so a caller can broadcast just THIS batch's deltas
|
|
75
|
+
* (`getDeltasInRange(firstSyncId - 1, lastSyncId, …)`) without racing concurrent
|
|
76
|
+
* commits with adjacent ids. `firstSyncId` is 0 when the batch produced no
|
|
77
|
+
* deltas (empty ops / all no-ops).
|
|
78
|
+
*/
|
|
79
|
+
export interface CommitResult {
|
|
80
|
+
lastSyncId: number;
|
|
81
|
+
firstSyncId: number;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@abloatai/ablo/server` — the host-side surface of the sync engine.
|
|
3
|
+
*
|
|
4
|
+
* Today this exposes the DataAdapter CONTRACT vocabulary (see {@link Row}).
|
|
5
|
+
* It will grow to hold the transport/storage-agnostic commit orchestration and
|
|
6
|
+
* the `Hub` core lifted out of `apps/sync-server` (see
|
|
7
|
+
* docs/plans/sync-engine-server-extraction-plan.md). The reference Postgres
|
|
8
|
+
* adapter (`executeCommit`/`selectAdapter`) and the WebSocket process lifecycle
|
|
9
|
+
* stay in the host — only the portable, driver-free pieces live here.
|
|
10
|
+
*/
|
|
11
|
+
export type { Row, ReadResult, SyncCursor, DataAdapterCapabilities, ProposalResult, ReadRequest, ChangeSet, SyncResult, DataAdapter, ProposableDataAdapter, AdapterResolver, } from './adapter.js';
|
|
12
|
+
export type { CommitContext, CommitResult } from './commit.js';
|
|
13
|
+
export { storageModeSchema, type StorageMode } from './storage-mode.js';
|
|
14
|
+
export type { ColumnOverride, BootstrapModel } from './read-config.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { storageModeSchema } from './storage-mode.js';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@abloatai/ablo/server/next` — mount Ablo's HTTP surface in a consumer's
|
|
3
|
+
* Next.js App Router, the Better-Auth `toNextJsHandler` way:
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* // app/api/ablo/[...all]/route.ts
|
|
7
|
+
* import { toAbloHandler } from '@abloatai/ablo/server/next';
|
|
8
|
+
* import { ablo } from '@/lib/ablo'; // your configured Ablo HTTP app
|
|
9
|
+
* export const { GET, POST } = toAbloHandler(ablo);
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Ablo's HTTP endpoints (bootstrap / query / the commit fallback) are stateless
|
|
13
|
+
* request→response, so they drop straight into a route handler. The realtime
|
|
14
|
+
* WebSocket channel is deliberately NOT served here — it is a persistent
|
|
15
|
+
* connection a serverless route handler cannot host, and stays an Ablo-hosted
|
|
16
|
+
* (or long-running Node) service. That is the HTTP/WSS split the architecture
|
|
17
|
+
* draws on purpose (see docs/plans/sync-gateway-audit-log-architecture.md).
|
|
18
|
+
*
|
|
19
|
+
* The package owns this MOUNT PRIMITIVE; the host owns the infra-bound app it
|
|
20
|
+
* wraps (the Hono app holding the Hub + routes + auth). Mirrors Better Auth,
|
|
21
|
+
* where `betterAuth()` builds the `.handler` and `toNextJsHandler` just maps it
|
|
22
|
+
* onto the HTTP verbs.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Anything that resolves a Web `Request` to a `Response` — e.g. a Hono app's
|
|
26
|
+
* `.fetch`, a Better-Auth-style `{ handler }`, or a bare function.
|
|
27
|
+
*/
|
|
28
|
+
export type AbloFetchHandler = (request: Request) => Response | Promise<Response>;
|
|
29
|
+
/** The shapes `toAbloHandler` accepts: a Hono-style `{ fetch }`, a
|
|
30
|
+
* `{ handler }`, or a bare `(Request) => Response`. */
|
|
31
|
+
export type AbloHttpApp = {
|
|
32
|
+
readonly fetch: AbloFetchHandler;
|
|
33
|
+
} | {
|
|
34
|
+
readonly handler: AbloFetchHandler;
|
|
35
|
+
} | AbloFetchHandler;
|
|
36
|
+
/** The route-handler object a Next.js App Router `route.ts` re-exports. One
|
|
37
|
+
* handler bound to every method the `[...all]` catch-all may receive. */
|
|
38
|
+
export interface AbloRouteHandlers {
|
|
39
|
+
readonly GET: AbloFetchHandler;
|
|
40
|
+
readonly POST: AbloFetchHandler;
|
|
41
|
+
readonly PATCH: AbloFetchHandler;
|
|
42
|
+
readonly PUT: AbloFetchHandler;
|
|
43
|
+
readonly DELETE: AbloFetchHandler;
|
|
44
|
+
readonly OPTIONS: AbloFetchHandler;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Wrap a configured Ablo HTTP app into Next.js App Router route handlers.
|
|
48
|
+
* Mirrors Better Auth's `toNextJsHandler` — accepts the app's `fetch`/`handler`
|
|
49
|
+
* (or a bare function) and returns one handler per verb.
|
|
50
|
+
*/
|
|
51
|
+
export declare function toAbloHandler(app: AbloHttpApp): AbloRouteHandlers;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@abloatai/ablo/server/next` — mount Ablo's HTTP surface in a consumer's
|
|
3
|
+
* Next.js App Router, the Better-Auth `toNextJsHandler` way:
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* // app/api/ablo/[...all]/route.ts
|
|
7
|
+
* import { toAbloHandler } from '@abloatai/ablo/server/next';
|
|
8
|
+
* import { ablo } from '@/lib/ablo'; // your configured Ablo HTTP app
|
|
9
|
+
* export const { GET, POST } = toAbloHandler(ablo);
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Ablo's HTTP endpoints (bootstrap / query / the commit fallback) are stateless
|
|
13
|
+
* request→response, so they drop straight into a route handler. The realtime
|
|
14
|
+
* WebSocket channel is deliberately NOT served here — it is a persistent
|
|
15
|
+
* connection a serverless route handler cannot host, and stays an Ablo-hosted
|
|
16
|
+
* (or long-running Node) service. That is the HTTP/WSS split the architecture
|
|
17
|
+
* draws on purpose (see docs/plans/sync-gateway-audit-log-architecture.md).
|
|
18
|
+
*
|
|
19
|
+
* The package owns this MOUNT PRIMITIVE; the host owns the infra-bound app it
|
|
20
|
+
* wraps (the Hono app holding the Hub + routes + auth). Mirrors Better Auth,
|
|
21
|
+
* where `betterAuth()` builds the `.handler` and `toNextJsHandler` just maps it
|
|
22
|
+
* onto the HTTP verbs.
|
|
23
|
+
*/
|
|
24
|
+
function resolveFetch(app) {
|
|
25
|
+
if (typeof app === 'function')
|
|
26
|
+
return app;
|
|
27
|
+
if ('fetch' in app)
|
|
28
|
+
return app.fetch;
|
|
29
|
+
return app.handler;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Wrap a configured Ablo HTTP app into Next.js App Router route handlers.
|
|
33
|
+
* Mirrors Better Auth's `toNextJsHandler` — accepts the app's `fetch`/`handler`
|
|
34
|
+
* (or a bare function) and returns one handler per verb.
|
|
35
|
+
*/
|
|
36
|
+
export function toAbloHandler(app) {
|
|
37
|
+
const fetch = resolveFetch(app);
|
|
38
|
+
const handler = (request) => fetch(request);
|
|
39
|
+
return {
|
|
40
|
+
GET: handler,
|
|
41
|
+
POST: handler,
|
|
42
|
+
PATCH: handler,
|
|
43
|
+
PUT: handler,
|
|
44
|
+
DELETE: handler,
|
|
45
|
+
OPTIONS: handler,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@abloatai/ablo/server` — per-model READ configuration the bootstrap
|
|
3
|
+
* reader consumes. Host-side type config (physical table, tenancy column,
|
|
4
|
+
* parent-scoping) — pure data, no `postgres` — so it lives in the server
|
|
5
|
+
* subpath and feeds the `DataAdapter` read contract. The SQL that consumes it
|
|
6
|
+
* (the bootstrap query builder) stays host-side.
|
|
7
|
+
*/
|
|
8
|
+
/** A field→column mapping with an explicit post-`SELECT *` alias. */
|
|
9
|
+
export interface ColumnOverride {
|
|
10
|
+
readonly field: string;
|
|
11
|
+
readonly column: string;
|
|
12
|
+
readonly alias: string;
|
|
13
|
+
}
|
|
14
|
+
export interface BootstrapModel {
|
|
15
|
+
name: string;
|
|
16
|
+
/**
|
|
17
|
+
* Additional names accepted for request-side lookup/filtering. In DB-canonical
|
|
18
|
+
* mode `name` stays the physical table name, while aliases cover generated
|
|
19
|
+
* compatibility names such as `WeatherReports` / `weatherReports`.
|
|
20
|
+
*/
|
|
21
|
+
aliases?: readonly string[];
|
|
22
|
+
/**
|
|
23
|
+
* Schema key used by source endpoints. `name` remains the wire/result model
|
|
24
|
+
* name, usually the typename; source handlers are keyed by the developer's
|
|
25
|
+
* schema object (`files`, `slideLayers`, ...).
|
|
26
|
+
*/
|
|
27
|
+
sourceModel?: string;
|
|
28
|
+
table: string;
|
|
29
|
+
syncGroups?: string[];
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
/** Max rows to return. Omit for unlimited. Maps to schema's bootstrapLimit. */
|
|
32
|
+
limit?: number;
|
|
33
|
+
/** SQL ORDER BY clause. Default: 'id'. Maps to schema's bootstrapOrderBy. */
|
|
34
|
+
orderBy?: string;
|
|
35
|
+
/** Whether the table has organization_id. Default: true. */
|
|
36
|
+
orgScoped?: boolean;
|
|
37
|
+
/** Physical tenancy column (default `organization_id`, configurable per model). */
|
|
38
|
+
orgColumn?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Parent-table scoping for rows with no `organization_id` column. Mirrors the
|
|
41
|
+
* schema's `scopedVia` option. When set, the bootstrap query emits:
|
|
42
|
+
*
|
|
43
|
+
* WHERE <table>.<localKey> IN
|
|
44
|
+
* (SELECT <parentKey> FROM <parentTable> WHERE <parentOrgColumn> = $1)
|
|
45
|
+
*
|
|
46
|
+
* Applied IN ADDITION TO whatever `orgScoped` dictates — so a table can have
|
|
47
|
+
* its own `organization_id` AND further narrow via a parent, though the common
|
|
48
|
+
* use is `orgScoped: false` + `scopedVia` on tables that lack the column.
|
|
49
|
+
*/
|
|
50
|
+
scopedVia?: {
|
|
51
|
+
localKey: string;
|
|
52
|
+
parentTable: string;
|
|
53
|
+
parentKey?: string;
|
|
54
|
+
parentOrgColumn?: string;
|
|
55
|
+
};
|
|
56
|
+
/** Client-facing field name → physical DB column for declared fields. */
|
|
57
|
+
fieldColumns?: Record<string, string>;
|
|
58
|
+
/** Physical-column aliases needed after SELECT * for `.from(...)` fields. */
|
|
59
|
+
columnOverrides?: readonly ColumnOverride[];
|
|
60
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@abloatai/ablo/server` — per-model READ configuration the bootstrap
|
|
3
|
+
* reader consumes. Host-side type config (physical table, tenancy column,
|
|
4
|
+
* parent-scoping) — pure data, no `postgres` — so it lives in the server
|
|
5
|
+
* subpath and feeds the `DataAdapter` read contract. The SQL that consumes it
|
|
6
|
+
* (the bootstrap query builder) stays host-side.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@abloatai/ablo/server` — the storage-mode vocabulary the `DataAdapter`
|
|
3
|
+
* contract supports. Analogous to Better Auth's adapter `id`/`adapterId`: a
|
|
4
|
+
* diagnostic discriminator on the adapter, NOT a routing switch (routing goes
|
|
5
|
+
* through the resolver/factory). The package owns this enum so the contract and
|
|
6
|
+
* every host adapter agree on the closed set:
|
|
7
|
+
* - `hosted` — Ablo's control-plane database.
|
|
8
|
+
* - `selfHosted` — the customer's database, same execution path as hosted.
|
|
9
|
+
* - `source` — a customer-owned endpoint (credentialless ingestion).
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
export declare const storageModeSchema: z.ZodEnum<{
|
|
13
|
+
source: "source";
|
|
14
|
+
hosted: "hosted";
|
|
15
|
+
selfHosted: "selfHosted";
|
|
16
|
+
}>;
|
|
17
|
+
export type StorageMode = z.infer<typeof storageModeSchema>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@abloatai/ablo/server` — the storage-mode vocabulary the `DataAdapter`
|
|
3
|
+
* contract supports. Analogous to Better Auth's adapter `id`/`adapterId`: a
|
|
4
|
+
* diagnostic discriminator on the adapter, NOT a routing switch (routing goes
|
|
5
|
+
* through the resolver/factory). The package owns this enum so the contract and
|
|
6
|
+
* every host adapter agree on the closed set:
|
|
7
|
+
* - `hosted` — Ablo's control-plane database.
|
|
8
|
+
* - `selfHosted` — the customer's database, same execution path as hosted.
|
|
9
|
+
* - `source` — a customer-owned endpoint (credentialless ingestion).
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
export const storageModeSchema = z.enum(['hosted', 'source', 'selfHosted']);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Data Source adapter — ONE interface every ORM backend implements, and the
|
|
3
|
+
* bridge that wires it into the core `dataSource()` handler.
|
|
4
|
+
*
|
|
5
|
+
* Pattern (Auth.js / Better Auth): one core interface, one package per ORM
|
|
6
|
+
* (`prismaDataSource`, `drizzleDataSource`, `kyselyDataSource`), each provably
|
|
7
|
+
* correct via the shared conformance suite. The adapter owns reading and writing
|
|
8
|
+
* the database, plus the transactional outbox and idempotency, so a customer never
|
|
9
|
+
* hand-writes them:
|
|
10
|
+
*
|
|
11
|
+
* export const POST = dataSource({
|
|
12
|
+
* schema, apiKey: process.env.ABLO_API_KEY!,
|
|
13
|
+
* ...sourceHandlersFromAdapter(prismaDataSource(prisma, schema), schema),
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* The bridge below connects the adapter to the core: it turns ONE adapter into the
|
|
17
|
+
* core handler's `commit` / `events` / per-model `load`+`list` — no per-ORM
|
|
18
|
+
* branching anywhere above the adapter.
|
|
19
|
+
*/
|
|
20
|
+
import type { SourceListQuery, SourceRequestContext } from './index.js';
|
|
21
|
+
import type { AdapterCapabilities, ChangeSet, EventsPage, Migration } from './contract.js';
|
|
22
|
+
/**
|
|
23
|
+
* A canonical row keyed by schema FIELD name (e.g. `operatorId`) — the SDK shape,
|
|
24
|
+
* NOT physical column names. Each adapter is the translation boundary: Prisma maps
|
|
25
|
+
* via its `@map`, Drizzle via the schema's `camelToSnake`/`column` rule. `unknown`
|
|
26
|
+
* leaf is narrowed by codegen later.
|
|
27
|
+
*/
|
|
28
|
+
export type Row = Record<string, unknown>;
|
|
29
|
+
/** A read against the canonical store — a single-row load or a filtered list. */
|
|
30
|
+
export type AdapterReadRequest = {
|
|
31
|
+
readonly kind: 'load';
|
|
32
|
+
readonly model: string;
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly scope?: SourceRequestContext;
|
|
35
|
+
} | {
|
|
36
|
+
readonly kind: 'list';
|
|
37
|
+
readonly model: string;
|
|
38
|
+
readonly query?: SourceListQuery;
|
|
39
|
+
readonly scope?: SourceRequestContext;
|
|
40
|
+
};
|
|
41
|
+
export interface AdapterCommitResult {
|
|
42
|
+
/** Canonical rows after the write — Ablo derives deltas from these. */
|
|
43
|
+
readonly rows: readonly Row[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The adapter interface. An ORM adapter implements exactly these. `read`/`commit`
|
|
47
|
+
* read and write the database; `events` reads the outbox; `migrations` ships the
|
|
48
|
+
* `ablo_idempotency` + `ablo_outbox` table-creation SQL so the customer never writes it.
|
|
49
|
+
*/
|
|
50
|
+
export interface DataSourceAdapter {
|
|
51
|
+
readonly capabilities: AdapterCapabilities;
|
|
52
|
+
/** The table-creation SQL the adapter ships for its own tables (`ablo_idempotency` + `ablo_outbox`). */
|
|
53
|
+
migrations(): readonly Migration[];
|
|
54
|
+
/** Canonical rows for a load/list. */
|
|
55
|
+
read(req: AdapterReadRequest): Promise<readonly Row[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Apply a change set transactionally and idempotently by `clientTxId`:
|
|
58
|
+
* a duplicate `clientTxId` returns the original rows without re-applying.
|
|
59
|
+
* Writes the `ablo_outbox` rows in the SAME transaction as the app rows.
|
|
60
|
+
*/
|
|
61
|
+
commit(change: ChangeSet): Promise<AdapterCommitResult>;
|
|
62
|
+
/** Read outbox events after `cursor` (null = from the beginning), up to `limit`. */
|
|
63
|
+
events(cursor: string | null, limit: number): Promise<EventsPage>;
|
|
64
|
+
}
|
|
65
|
+
export type { AdapterCapabilities, ChangeSet, Migration, OutboxEvent, EventsPage } from './contract.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Data Source adapter — ONE interface every ORM backend implements, and the
|
|
3
|
+
* bridge that wires it into the core `dataSource()` handler.
|
|
4
|
+
*
|
|
5
|
+
* Pattern (Auth.js / Better Auth): one core interface, one package per ORM
|
|
6
|
+
* (`prismaDataSource`, `drizzleDataSource`, `kyselyDataSource`), each provably
|
|
7
|
+
* correct via the shared conformance suite. The adapter owns reading and writing
|
|
8
|
+
* the database, plus the transactional outbox and idempotency, so a customer never
|
|
9
|
+
* hand-writes them:
|
|
10
|
+
*
|
|
11
|
+
* export const POST = dataSource({
|
|
12
|
+
* schema, apiKey: process.env.ABLO_API_KEY!,
|
|
13
|
+
* ...sourceHandlersFromAdapter(prismaDataSource(prisma, schema), schema),
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* The bridge below connects the adapter to the core: it turns ONE adapter into the
|
|
17
|
+
* core handler's `commit` / `events` / per-model `load`+`list` — no per-ORM
|
|
18
|
+
* branching anywhere above the adapter.
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drizzle Data Source adapter. Same adapter interface + conformance as `prismaDataSource`,
|
|
3
|
+
* built against Drizzle's REAL API (read from drizzle-orm's own source/docs):
|
|
4
|
+
* - `db.transaction(async (tx) => …)` — interactive transaction (commit/rollback).
|
|
5
|
+
* - `db.execute(sql`…`)` — parametrized raw SQL; `sql.identifier()` safely quotes
|
|
6
|
+
* dynamic table/column names, `sql`${value}`` parametrizes values.
|
|
7
|
+
*
|
|
8
|
+
* SCHEMA-DRIVEN COLUMNS. Unlike Prisma — whose delegate applies the model's
|
|
9
|
+
* `@map` for free — this adapter writes raw SQL, so it would otherwise bypass any
|
|
10
|
+
* field→column translation. It therefore derives every table + column name from
|
|
11
|
+
* the SAME rule the provisioner uses (`generateProvisionPlan`):
|
|
12
|
+
* table = `model.tableName ?? key`
|
|
13
|
+
* column = `fieldMeta.column ?? camelToSnake(field)` (+ the model's tenancy column)
|
|
14
|
+
* so `ablo migrate` (which emits `operator_id`) and this adapter (which now writes
|
|
15
|
+
* `operator_id`) COMPOSE. Define the schema once, point Ablo at your Postgres —
|
|
16
|
+
* no hand-written parallel Drizzle table. The adapter is the translation boundary:
|
|
17
|
+
* its public surface (rows in/out, outbox `data`) is field-keyed (the SDK shape);
|
|
18
|
+
* the physical columns it reads/writes are snake_case.
|
|
19
|
+
*
|
|
20
|
+
* IMPORTANT GOTCHAS (from drizzle-orm docs):
|
|
21
|
+
* 1. Interactive `db.transaction` requires a driver that supports it. Neon's
|
|
22
|
+
* `neon-http` driver does NOT (single-shot only) — use `neon-serverless`
|
|
23
|
+
* (WebSocket) or `pg`. With neon-http the commit path throws at runtime.
|
|
24
|
+
* 2. `db.execute` result shape is driver-specific (postgres-js returns an
|
|
25
|
+
* array-like RowList; node-postgres returns `{ rows }`). `rowsOf()`
|
|
26
|
+
* normalizes both.
|
|
27
|
+
*
|
|
28
|
+
* We use `sql` + `db.execute` for ALL writes (not the fluent builder) so the
|
|
29
|
+
* adapter is one small, fully-typed unit with no per-driver builder generics.
|
|
30
|
+
*/
|
|
31
|
+
import { type SQL } from 'drizzle-orm';
|
|
32
|
+
import type { DataSourceAdapter, Row } from '../adapter.js';
|
|
33
|
+
import type { Schema, SchemaRecord } from '../../schema/schema.js';
|
|
34
|
+
/** The subset of a Drizzle database/transaction handle the adapter calls. */
|
|
35
|
+
export interface DrizzleLike {
|
|
36
|
+
execute(query: SQL): Promise<DrizzleExecuteResult>;
|
|
37
|
+
transaction<T>(fn: (tx: DrizzleLike) => Promise<T>): Promise<T>;
|
|
38
|
+
}
|
|
39
|
+
/** `db.execute` is array-like (postgres-js) or `{ rows }` (node-postgres). */
|
|
40
|
+
export type DrizzleExecuteResult = readonly Row[] | {
|
|
41
|
+
readonly rows: readonly Row[];
|
|
42
|
+
};
|
|
43
|
+
export declare function drizzleDataSource<S extends SchemaRecord>(db: DrizzleLike, schema: Schema<S>): DataSourceAdapter;
|