@clivly/sdk 0.12.0 → 0.13.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.
- package/dist/chat-session.d.cts +1 -1
- package/dist/chat-session.d.mts +1 -1
- package/dist/drizzle.cjs +65 -0
- package/dist/drizzle.d.cts +51 -1
- package/dist/drizzle.d.mts +51 -1
- package/dist/drizzle.mjs +64 -1
- package/dist/index.cjs +38 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +38 -1
- package/dist/{types-CvhxV-IX.d.cts → types-DK8mqmkV.d.cts} +42 -0
- package/dist/{types-CvhxV-IX.d.mts → types-DK8mqmkV.d.mts} +42 -0
- package/dist/vite.d.cts +1 -1
- package/dist/vite.d.mts +1 -1
- package/package.json +2 -2
package/dist/chat-session.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as CreateChatSessionHandlerOptions } from "./types-
|
|
1
|
+
import { c as CreateChatSessionHandlerOptions } from "./types-DK8mqmkV.cjs";
|
|
2
2
|
//#region src/chat-session.d.ts
|
|
3
3
|
export declare function createChatSessionHandler({ allowedOrigins, apiKey, apiUrl, fetchImpl, resolveUser }: CreateChatSessionHandlerOptions): (request: Request) => Promise<Response>;
|
|
4
4
|
//#endregion
|
package/dist/chat-session.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as CreateChatSessionHandlerOptions } from "./types-
|
|
1
|
+
import { c as CreateChatSessionHandlerOptions } from "./types-DK8mqmkV.mjs";
|
|
2
2
|
//#region src/chat-session.d.ts
|
|
3
3
|
export declare function createChatSessionHandler({ allowedOrigins, apiKey, apiUrl, fetchImpl, resolveUser }: CreateChatSessionHandlerOptions): (request: Request) => Promise<Response>;
|
|
4
4
|
//#endregion
|
package/dist/drizzle.cjs
CHANGED
|
@@ -163,6 +163,69 @@ function fromDrizzle(db, table, opts) {
|
|
|
163
163
|
return source;
|
|
164
164
|
}
|
|
165
165
|
/**
|
|
166
|
+
* Insert a row into a mapped Drizzle table and push it to Clivly immediately
|
|
167
|
+
* — the "one call does both" instant-push API (see
|
|
168
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md, question 1). Returns
|
|
169
|
+
* only after the write completes; the push itself is fire-and-forget (see
|
|
170
|
+
* `pushOne`), so this resolves at the same time a plain `db.insert(...)`
|
|
171
|
+
* would.
|
|
172
|
+
*
|
|
173
|
+
* Created once per mapped table, mirroring `fromDrizzle`'s own convention —
|
|
174
|
+
* no per-call table-to-entity lookup, so a table two entities share (e.g.
|
|
175
|
+
* `user` for both `user` and `organizationOwner`) is never ambiguous:
|
|
176
|
+
* ```ts
|
|
177
|
+
* const insertUser = insertAndPush(clivly, db, schema.user, { entity: "user" });
|
|
178
|
+
* // in the signup handler:
|
|
179
|
+
* const created = await insertUser(newUserValues, c.executionCtx);
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* `waitUntil`, when provided (Hono exposes it as `c.executionCtx.waitUntil`),
|
|
183
|
+
* extends the request's lifetime past the response so the push survives on
|
|
184
|
+
* Cloudflare Workers and similar runtimes, which otherwise kill background
|
|
185
|
+
* work the instant a response is sent. Omit it on a long-lived process where
|
|
186
|
+
* nothing tears the request down early.
|
|
187
|
+
*
|
|
188
|
+
* Table property keys are translated to real database column names before
|
|
189
|
+
* the push — the same translation `fromDrizzle` performs on `fetchPage` rows
|
|
190
|
+
* — because a raw `.returning()` result is keyed by Drizzle property name
|
|
191
|
+
* (`fullName`), and every consumer downstream of a push (the fieldMap, the
|
|
192
|
+
* cloud's ingest) addresses columns by their database name (`full_name`).
|
|
193
|
+
* Skipping this would silently push empty fields with no error anywhere.
|
|
194
|
+
*/
|
|
195
|
+
/**
|
|
196
|
+
* Translate a Drizzle-property-keyed row (e.g. a `.returning()` result, or a
|
|
197
|
+
* plain unprojected `select()`) to real database column names — the same
|
|
198
|
+
* translation `fromDrizzle` performs on `fetchPage` rows. Every consumer
|
|
199
|
+
* downstream of a push (the fieldMap, the cloud's ingest) addresses columns
|
|
200
|
+
* by their database name (`full_name`), not the property name (`fullName`);
|
|
201
|
+
* skipping this would silently push empty fields with no error anywhere.
|
|
202
|
+
*
|
|
203
|
+
* Exported so a host can build the same translated row from a table it read
|
|
204
|
+
* some other way — e.g. re-querying a row inside an auth library's own
|
|
205
|
+
* lifecycle hook, where there is no `insertAndPush` call site to wrap
|
|
206
|
+
* because the library performs the insert internally.
|
|
207
|
+
*/
|
|
208
|
+
function toColumnKeyedRow(table, row) {
|
|
209
|
+
const columns = (0, drizzle_orm.getTableColumns)(table);
|
|
210
|
+
const columnNameByPropertyKey = /* @__PURE__ */ new Map();
|
|
211
|
+
for (const [propertyKey, column] of Object.entries(columns)) columnNameByPropertyKey.set(propertyKey, column.name);
|
|
212
|
+
const out = {};
|
|
213
|
+
for (const [propertyKey, value] of Object.entries(row)) out[columnNameByPropertyKey.get(propertyKey) ?? propertyKey] = value;
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
function insertAndPush(clivly, db, table, opts) {
|
|
217
|
+
const insertableDb = db;
|
|
218
|
+
return async (values, waitUntil) => {
|
|
219
|
+
const [created] = await insertableDb.insert(table).values(values).returning();
|
|
220
|
+
if (!created) throw new Error(`insertAndPush: insert into "${(0, drizzle_orm.getTableName)(table)}" returned no row.`);
|
|
221
|
+
const row = toColumnKeyedRow(table, created);
|
|
222
|
+
const pushed = clivly.pushOne(opts.entity, row);
|
|
223
|
+
if (waitUntil) waitUntil(pushed);
|
|
224
|
+
else await pushed;
|
|
225
|
+
return created;
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
166
229
|
* Build a `NamespaceIntrospector` from any Drizzle client that exposes
|
|
167
230
|
* `.execute()`. Reads `information_schema` in the connection's default schema
|
|
168
231
|
* (`current_schema()`), so it reports exactly the tables the app can reach.
|
|
@@ -207,3 +270,5 @@ Object.defineProperty(exports, "discoverFromDrizzle", {
|
|
|
207
270
|
exports.drizzleIntrospector = drizzleIntrospector;
|
|
208
271
|
exports.fromDrizzle = fromDrizzle;
|
|
209
272
|
exports.fromDrizzleProjection = fromDrizzleProjection;
|
|
273
|
+
exports.insertAndPush = insertAndPush;
|
|
274
|
+
exports.toColumnKeyedRow = toColumnKeyedRow;
|
package/dist/drizzle.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as SyncSource, y as NamespaceIntrospector } from "./types-
|
|
1
|
+
import { _ as SyncSource, y as NamespaceIntrospector } from "./types-DK8mqmkV.cjs";
|
|
2
2
|
import { ProjectionMapping, ProjectionRef } from "@clivly/core";
|
|
3
3
|
import { Table } from "drizzle-orm";
|
|
4
4
|
import { buildUniqueConstraints, discoverFromDrizzle } from "@clivly/core/drizzle";
|
|
@@ -115,6 +115,56 @@ export declare function fromDrizzle(db: DrizzleDb, table: Table, opts: FromDrizz
|
|
|
115
115
|
objectType: string;
|
|
116
116
|
};
|
|
117
117
|
export declare function fromDrizzle(db: DrizzleDb, table: Table, opts: FromDrizzleOptions): SyncSource;
|
|
118
|
+
interface PushOneCapable {
|
|
119
|
+
pushOne(entityKey: string, row: Record<string, unknown>): Promise<void>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Insert a row into a mapped Drizzle table and push it to Clivly immediately
|
|
123
|
+
* — the "one call does both" instant-push API (see
|
|
124
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md, question 1). Returns
|
|
125
|
+
* only after the write completes; the push itself is fire-and-forget (see
|
|
126
|
+
* `pushOne`), so this resolves at the same time a plain `db.insert(...)`
|
|
127
|
+
* would.
|
|
128
|
+
*
|
|
129
|
+
* Created once per mapped table, mirroring `fromDrizzle`'s own convention —
|
|
130
|
+
* no per-call table-to-entity lookup, so a table two entities share (e.g.
|
|
131
|
+
* `user` for both `user` and `organizationOwner`) is never ambiguous:
|
|
132
|
+
* ```ts
|
|
133
|
+
* const insertUser = insertAndPush(clivly, db, schema.user, { entity: "user" });
|
|
134
|
+
* // in the signup handler:
|
|
135
|
+
* const created = await insertUser(newUserValues, c.executionCtx);
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
* `waitUntil`, when provided (Hono exposes it as `c.executionCtx.waitUntil`),
|
|
139
|
+
* extends the request's lifetime past the response so the push survives on
|
|
140
|
+
* Cloudflare Workers and similar runtimes, which otherwise kill background
|
|
141
|
+
* work the instant a response is sent. Omit it on a long-lived process where
|
|
142
|
+
* nothing tears the request down early.
|
|
143
|
+
*
|
|
144
|
+
* Table property keys are translated to real database column names before
|
|
145
|
+
* the push — the same translation `fromDrizzle` performs on `fetchPage` rows
|
|
146
|
+
* — because a raw `.returning()` result is keyed by Drizzle property name
|
|
147
|
+
* (`fullName`), and every consumer downstream of a push (the fieldMap, the
|
|
148
|
+
* cloud's ingest) addresses columns by their database name (`full_name`).
|
|
149
|
+
* Skipping this would silently push empty fields with no error anywhere.
|
|
150
|
+
*/
|
|
151
|
+
/**
|
|
152
|
+
* Translate a Drizzle-property-keyed row (e.g. a `.returning()` result, or a
|
|
153
|
+
* plain unprojected `select()`) to real database column names — the same
|
|
154
|
+
* translation `fromDrizzle` performs on `fetchPage` rows. Every consumer
|
|
155
|
+
* downstream of a push (the fieldMap, the cloud's ingest) addresses columns
|
|
156
|
+
* by their database name (`full_name`), not the property name (`fullName`);
|
|
157
|
+
* skipping this would silently push empty fields with no error anywhere.
|
|
158
|
+
*
|
|
159
|
+
* Exported so a host can build the same translated row from a table it read
|
|
160
|
+
* some other way — e.g. re-querying a row inside an auth library's own
|
|
161
|
+
* lifecycle hook, where there is no `insertAndPush` call site to wrap
|
|
162
|
+
* because the library performs the insert internally.
|
|
163
|
+
*/
|
|
164
|
+
export declare function toColumnKeyedRow(table: Table, row: Record<string, unknown>): Record<string, unknown>;
|
|
165
|
+
export declare function insertAndPush(clivly: PushOneCapable, db: unknown, table: Table, opts: {
|
|
166
|
+
entity: string;
|
|
167
|
+
}): (values: Record<string, unknown>, waitUntil?: (promise: Promise<unknown>) => void) => Promise<Record<string, unknown>>;
|
|
118
168
|
/**
|
|
119
169
|
* Build a `NamespaceIntrospector` from any Drizzle client that exposes
|
|
120
170
|
* `.execute()`. Reads `information_schema` in the connection's default schema
|
package/dist/drizzle.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as SyncSource, y as NamespaceIntrospector } from "./types-
|
|
1
|
+
import { _ as SyncSource, y as NamespaceIntrospector } from "./types-DK8mqmkV.mjs";
|
|
2
2
|
import { ProjectionMapping, ProjectionRef } from "@clivly/core";
|
|
3
3
|
import { buildUniqueConstraints, discoverFromDrizzle } from "@clivly/core/drizzle";
|
|
4
4
|
import { Table } from "drizzle-orm";
|
|
@@ -115,6 +115,56 @@ export declare function fromDrizzle(db: DrizzleDb, table: Table, opts: FromDrizz
|
|
|
115
115
|
objectType: string;
|
|
116
116
|
};
|
|
117
117
|
export declare function fromDrizzle(db: DrizzleDb, table: Table, opts: FromDrizzleOptions): SyncSource;
|
|
118
|
+
interface PushOneCapable {
|
|
119
|
+
pushOne(entityKey: string, row: Record<string, unknown>): Promise<void>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Insert a row into a mapped Drizzle table and push it to Clivly immediately
|
|
123
|
+
* — the "one call does both" instant-push API (see
|
|
124
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md, question 1). Returns
|
|
125
|
+
* only after the write completes; the push itself is fire-and-forget (see
|
|
126
|
+
* `pushOne`), so this resolves at the same time a plain `db.insert(...)`
|
|
127
|
+
* would.
|
|
128
|
+
*
|
|
129
|
+
* Created once per mapped table, mirroring `fromDrizzle`'s own convention —
|
|
130
|
+
* no per-call table-to-entity lookup, so a table two entities share (e.g.
|
|
131
|
+
* `user` for both `user` and `organizationOwner`) is never ambiguous:
|
|
132
|
+
* ```ts
|
|
133
|
+
* const insertUser = insertAndPush(clivly, db, schema.user, { entity: "user" });
|
|
134
|
+
* // in the signup handler:
|
|
135
|
+
* const created = await insertUser(newUserValues, c.executionCtx);
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
* `waitUntil`, when provided (Hono exposes it as `c.executionCtx.waitUntil`),
|
|
139
|
+
* extends the request's lifetime past the response so the push survives on
|
|
140
|
+
* Cloudflare Workers and similar runtimes, which otherwise kill background
|
|
141
|
+
* work the instant a response is sent. Omit it on a long-lived process where
|
|
142
|
+
* nothing tears the request down early.
|
|
143
|
+
*
|
|
144
|
+
* Table property keys are translated to real database column names before
|
|
145
|
+
* the push — the same translation `fromDrizzle` performs on `fetchPage` rows
|
|
146
|
+
* — because a raw `.returning()` result is keyed by Drizzle property name
|
|
147
|
+
* (`fullName`), and every consumer downstream of a push (the fieldMap, the
|
|
148
|
+
* cloud's ingest) addresses columns by their database name (`full_name`).
|
|
149
|
+
* Skipping this would silently push empty fields with no error anywhere.
|
|
150
|
+
*/
|
|
151
|
+
/**
|
|
152
|
+
* Translate a Drizzle-property-keyed row (e.g. a `.returning()` result, or a
|
|
153
|
+
* plain unprojected `select()`) to real database column names — the same
|
|
154
|
+
* translation `fromDrizzle` performs on `fetchPage` rows. Every consumer
|
|
155
|
+
* downstream of a push (the fieldMap, the cloud's ingest) addresses columns
|
|
156
|
+
* by their database name (`full_name`), not the property name (`fullName`);
|
|
157
|
+
* skipping this would silently push empty fields with no error anywhere.
|
|
158
|
+
*
|
|
159
|
+
* Exported so a host can build the same translated row from a table it read
|
|
160
|
+
* some other way — e.g. re-querying a row inside an auth library's own
|
|
161
|
+
* lifecycle hook, where there is no `insertAndPush` call site to wrap
|
|
162
|
+
* because the library performs the insert internally.
|
|
163
|
+
*/
|
|
164
|
+
export declare function toColumnKeyedRow(table: Table, row: Record<string, unknown>): Record<string, unknown>;
|
|
165
|
+
export declare function insertAndPush(clivly: PushOneCapable, db: unknown, table: Table, opts: {
|
|
166
|
+
entity: string;
|
|
167
|
+
}): (values: Record<string, unknown>, waitUntil?: (promise: Promise<unknown>) => void) => Promise<Record<string, unknown>>;
|
|
118
168
|
/**
|
|
119
169
|
* Build a `NamespaceIntrospector` from any Drizzle client that exposes
|
|
120
170
|
* `.execute()`. Reads `information_schema` in the connection's default schema
|
package/dist/drizzle.mjs
CHANGED
|
@@ -162,6 +162,69 @@ function fromDrizzle(db, table, opts) {
|
|
|
162
162
|
return source;
|
|
163
163
|
}
|
|
164
164
|
/**
|
|
165
|
+
* Insert a row into a mapped Drizzle table and push it to Clivly immediately
|
|
166
|
+
* — the "one call does both" instant-push API (see
|
|
167
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md, question 1). Returns
|
|
168
|
+
* only after the write completes; the push itself is fire-and-forget (see
|
|
169
|
+
* `pushOne`), so this resolves at the same time a plain `db.insert(...)`
|
|
170
|
+
* would.
|
|
171
|
+
*
|
|
172
|
+
* Created once per mapped table, mirroring `fromDrizzle`'s own convention —
|
|
173
|
+
* no per-call table-to-entity lookup, so a table two entities share (e.g.
|
|
174
|
+
* `user` for both `user` and `organizationOwner`) is never ambiguous:
|
|
175
|
+
* ```ts
|
|
176
|
+
* const insertUser = insertAndPush(clivly, db, schema.user, { entity: "user" });
|
|
177
|
+
* // in the signup handler:
|
|
178
|
+
* const created = await insertUser(newUserValues, c.executionCtx);
|
|
179
|
+
* ```
|
|
180
|
+
*
|
|
181
|
+
* `waitUntil`, when provided (Hono exposes it as `c.executionCtx.waitUntil`),
|
|
182
|
+
* extends the request's lifetime past the response so the push survives on
|
|
183
|
+
* Cloudflare Workers and similar runtimes, which otherwise kill background
|
|
184
|
+
* work the instant a response is sent. Omit it on a long-lived process where
|
|
185
|
+
* nothing tears the request down early.
|
|
186
|
+
*
|
|
187
|
+
* Table property keys are translated to real database column names before
|
|
188
|
+
* the push — the same translation `fromDrizzle` performs on `fetchPage` rows
|
|
189
|
+
* — because a raw `.returning()` result is keyed by Drizzle property name
|
|
190
|
+
* (`fullName`), and every consumer downstream of a push (the fieldMap, the
|
|
191
|
+
* cloud's ingest) addresses columns by their database name (`full_name`).
|
|
192
|
+
* Skipping this would silently push empty fields with no error anywhere.
|
|
193
|
+
*/
|
|
194
|
+
/**
|
|
195
|
+
* Translate a Drizzle-property-keyed row (e.g. a `.returning()` result, or a
|
|
196
|
+
* plain unprojected `select()`) to real database column names — the same
|
|
197
|
+
* translation `fromDrizzle` performs on `fetchPage` rows. Every consumer
|
|
198
|
+
* downstream of a push (the fieldMap, the cloud's ingest) addresses columns
|
|
199
|
+
* by their database name (`full_name`), not the property name (`fullName`);
|
|
200
|
+
* skipping this would silently push empty fields with no error anywhere.
|
|
201
|
+
*
|
|
202
|
+
* Exported so a host can build the same translated row from a table it read
|
|
203
|
+
* some other way — e.g. re-querying a row inside an auth library's own
|
|
204
|
+
* lifecycle hook, where there is no `insertAndPush` call site to wrap
|
|
205
|
+
* because the library performs the insert internally.
|
|
206
|
+
*/
|
|
207
|
+
function toColumnKeyedRow(table, row) {
|
|
208
|
+
const columns = getTableColumns(table);
|
|
209
|
+
const columnNameByPropertyKey = /* @__PURE__ */ new Map();
|
|
210
|
+
for (const [propertyKey, column] of Object.entries(columns)) columnNameByPropertyKey.set(propertyKey, column.name);
|
|
211
|
+
const out = {};
|
|
212
|
+
for (const [propertyKey, value] of Object.entries(row)) out[columnNameByPropertyKey.get(propertyKey) ?? propertyKey] = value;
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
function insertAndPush(clivly, db, table, opts) {
|
|
216
|
+
const insertableDb = db;
|
|
217
|
+
return async (values, waitUntil) => {
|
|
218
|
+
const [created] = await insertableDb.insert(table).values(values).returning();
|
|
219
|
+
if (!created) throw new Error(`insertAndPush: insert into "${getTableName(table)}" returned no row.`);
|
|
220
|
+
const row = toColumnKeyedRow(table, created);
|
|
221
|
+
const pushed = clivly.pushOne(opts.entity, row);
|
|
222
|
+
if (waitUntil) waitUntil(pushed);
|
|
223
|
+
else await pushed;
|
|
224
|
+
return created;
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
165
228
|
* Build a `NamespaceIntrospector` from any Drizzle client that exposes
|
|
166
229
|
* `.execute()`. Reads `information_schema` in the connection's default schema
|
|
167
230
|
* (`current_schema()`), so it reports exactly the tables the app can reach.
|
|
@@ -191,4 +254,4 @@ function drizzleIntrospector(db) {
|
|
|
191
254
|
};
|
|
192
255
|
}
|
|
193
256
|
//#endregion
|
|
194
|
-
export { buildUniqueConstraints, discoverFromDrizzle, drizzleIntrospector, fromDrizzle, fromDrizzleProjection };
|
|
257
|
+
export { buildUniqueConstraints, discoverFromDrizzle, drizzleIntrospector, fromDrizzle, fromDrizzleProjection, insertAndPush, toColumnKeyedRow };
|
package/dist/index.cjs
CHANGED
|
@@ -701,6 +701,20 @@ function detectHostEnvironment(env, configured) {
|
|
|
701
701
|
return fromNodeEnv(env.NODE_ENV?.trim());
|
|
702
702
|
}
|
|
703
703
|
//#endregion
|
|
704
|
+
//#region src/instant-push-support.ts
|
|
705
|
+
/**
|
|
706
|
+
* The fast rollback switch for instant push (see
|
|
707
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md, question 5).
|
|
708
|
+
*
|
|
709
|
+
* A host-side env var, not a Clivly-Cloud-side toggle: it must keep working
|
|
710
|
+
* even when Clivly Cloud is the thing misbehaving (e.g. its ingest endpoint
|
|
711
|
+
* is what instant push is flooding), and it must take effect without a
|
|
712
|
+
* redeploy — just an env var change and the next cold start.
|
|
713
|
+
*/
|
|
714
|
+
function isInstantPushDisabled(env) {
|
|
715
|
+
return env.CLIVLY_INSTANT_PUSH_DISABLED === "true";
|
|
716
|
+
}
|
|
717
|
+
//#endregion
|
|
704
718
|
//#region src/presence-machine.ts
|
|
705
719
|
const PRESENCE_BACKOFF_BASE_MS = 1e3;
|
|
706
720
|
const PRESENCE_BACKOFF_MAX_MS = 3e4;
|
|
@@ -1972,6 +1986,28 @@ function createClivlySDK(config) {
|
|
|
1972
1986
|
lastPushFailure = null;
|
|
1973
1987
|
if (!await syncOne(resolved.source, resolved.path, "delta", resolved.objectType, runId, SAMPLE_BATCH_SIZE, SAMPLE_BATCH_SIZE, false)) throw pushFailureError(describePushFailure([resolved.source.entity]));
|
|
1974
1988
|
};
|
|
1989
|
+
const pushOne = async (entityKey, row) => {
|
|
1990
|
+
if (config.sync?.instant !== true) return;
|
|
1991
|
+
if (isInstantPushDisabled(config.env ?? process.env)) return;
|
|
1992
|
+
const resolved = resolveEntitySource(config, entityKey);
|
|
1993
|
+
if (!resolved) {
|
|
1994
|
+
console.warn(`Clivly instant push: no source is configured for entity "${entityKey}" — this row was not pushed. It will still sync on the next delta run once \`entities\` declares it.`);
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
const path = CONCEPT_PATHS[resolved.expectedKey];
|
|
1998
|
+
if (!path) return;
|
|
1999
|
+
const push = createPush();
|
|
2000
|
+
try {
|
|
2001
|
+
if (!await push(path, {
|
|
2002
|
+
source: pushSourceOf(resolved.source),
|
|
2003
|
+
rows: [row],
|
|
2004
|
+
mode: "delta",
|
|
2005
|
+
...resolved.expectedKey === "custom" ? { objectType: resolved.source.objectType } : {}
|
|
2006
|
+
})) console.warn(`Clivly instant push failed for "${entityKey}". This row will still sync on the next delta run.`);
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
console.warn(`Clivly instant push failed for "${entityKey}": ${error instanceof Error ? error.message : String(error)}. This row will still sync on the next delta run.`);
|
|
2009
|
+
}
|
|
2010
|
+
};
|
|
1975
2011
|
const describeSyncTrigger = () => ({
|
|
1976
2012
|
configured: config.syncTrigger?.url,
|
|
1977
2013
|
resolved: resolveTriggerUrl({
|
|
@@ -2327,7 +2363,8 @@ function createClivlySDK(config) {
|
|
|
2327
2363
|
runScheduled,
|
|
2328
2364
|
createClivlyHandler,
|
|
2329
2365
|
sync: runSync,
|
|
2330
|
-
createAuthVerifyHandler
|
|
2366
|
+
createAuthVerifyHandler,
|
|
2367
|
+
pushOne
|
|
2331
2368
|
};
|
|
2332
2369
|
}
|
|
2333
2370
|
//#endregion
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ClivlySDK, c as CreateChatSessionHandlerOptions, d as DoctorCheck, f as DoctorReport, g as RemoteProjectionsDescription, h as EnsuredObjectTypeResult, i as ClivlyConnectResult, l as CursorStore, m as EnsuredMappingResult, n as ChatWidgetVisitor, o as ClivlySDKConfig, p as EnsureSchemaResult, r as ClivlyConnectReason, s as ClivlyUser, t as ChatSessionRequestBody, u as DiscoveredTable, v as SyncTriggerDescription } from "./types-
|
|
1
|
+
import { a as ClivlySDK, c as CreateChatSessionHandlerOptions, d as DoctorCheck, f as DoctorReport, g as RemoteProjectionsDescription, h as EnsuredObjectTypeResult, i as ClivlyConnectResult, l as CursorStore, m as EnsuredMappingResult, n as ChatWidgetVisitor, o as ClivlySDKConfig, p as EnsureSchemaResult, r as ClivlyConnectReason, s as ClivlyUser, t as ChatSessionRequestBody, u as DiscoveredTable, v as SyncTriggerDescription } from "./types-DK8mqmkV.cjs";
|
|
2
2
|
import { createChatSessionHandler } from "./chat-session.cjs";
|
|
3
3
|
import { ClivlyEntitiesConfig, ConfigValidationError, ConfigValidationResult, CustomObjectDeclaration, DiscoveredSchemaTable, customObjectDeclarations, defineClivlyConfig, validateEntitiesConfig } from "@clivly/core/entity-config";
|
|
4
4
|
//#region src/trigger-url.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ClivlySDK, c as CreateChatSessionHandlerOptions, d as DoctorCheck, f as DoctorReport, g as RemoteProjectionsDescription, h as EnsuredObjectTypeResult, i as ClivlyConnectResult, l as CursorStore, m as EnsuredMappingResult, n as ChatWidgetVisitor, o as ClivlySDKConfig, p as EnsureSchemaResult, r as ClivlyConnectReason, s as ClivlyUser, t as ChatSessionRequestBody, u as DiscoveredTable, v as SyncTriggerDescription } from "./types-
|
|
1
|
+
import { a as ClivlySDK, c as CreateChatSessionHandlerOptions, d as DoctorCheck, f as DoctorReport, g as RemoteProjectionsDescription, h as EnsuredObjectTypeResult, i as ClivlyConnectResult, l as CursorStore, m as EnsuredMappingResult, n as ChatWidgetVisitor, o as ClivlySDKConfig, p as EnsureSchemaResult, r as ClivlyConnectReason, s as ClivlyUser, t as ChatSessionRequestBody, u as DiscoveredTable, v as SyncTriggerDescription } from "./types-DK8mqmkV.mjs";
|
|
2
2
|
import { createChatSessionHandler } from "./chat-session.mjs";
|
|
3
3
|
import { ClivlyEntitiesConfig, ConfigValidationError, ConfigValidationResult, CustomObjectDeclaration, DiscoveredSchemaTable, customObjectDeclarations, defineClivlyConfig, validateEntitiesConfig } from "@clivly/core/entity-config";
|
|
4
4
|
//#region src/trigger-url.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -700,6 +700,20 @@ function detectHostEnvironment(env, configured) {
|
|
|
700
700
|
return fromNodeEnv(env.NODE_ENV?.trim());
|
|
701
701
|
}
|
|
702
702
|
//#endregion
|
|
703
|
+
//#region src/instant-push-support.ts
|
|
704
|
+
/**
|
|
705
|
+
* The fast rollback switch for instant push (see
|
|
706
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md, question 5).
|
|
707
|
+
*
|
|
708
|
+
* A host-side env var, not a Clivly-Cloud-side toggle: it must keep working
|
|
709
|
+
* even when Clivly Cloud is the thing misbehaving (e.g. its ingest endpoint
|
|
710
|
+
* is what instant push is flooding), and it must take effect without a
|
|
711
|
+
* redeploy — just an env var change and the next cold start.
|
|
712
|
+
*/
|
|
713
|
+
function isInstantPushDisabled(env) {
|
|
714
|
+
return env.CLIVLY_INSTANT_PUSH_DISABLED === "true";
|
|
715
|
+
}
|
|
716
|
+
//#endregion
|
|
703
717
|
//#region src/presence-machine.ts
|
|
704
718
|
const PRESENCE_BACKOFF_BASE_MS = 1e3;
|
|
705
719
|
const PRESENCE_BACKOFF_MAX_MS = 3e4;
|
|
@@ -1971,6 +1985,28 @@ function createClivlySDK(config) {
|
|
|
1971
1985
|
lastPushFailure = null;
|
|
1972
1986
|
if (!await syncOne(resolved.source, resolved.path, "delta", resolved.objectType, runId, SAMPLE_BATCH_SIZE, SAMPLE_BATCH_SIZE, false)) throw pushFailureError(describePushFailure([resolved.source.entity]));
|
|
1973
1987
|
};
|
|
1988
|
+
const pushOne = async (entityKey, row) => {
|
|
1989
|
+
if (config.sync?.instant !== true) return;
|
|
1990
|
+
if (isInstantPushDisabled(config.env ?? process.env)) return;
|
|
1991
|
+
const resolved = resolveEntitySource(config, entityKey);
|
|
1992
|
+
if (!resolved) {
|
|
1993
|
+
console.warn(`Clivly instant push: no source is configured for entity "${entityKey}" — this row was not pushed. It will still sync on the next delta run once \`entities\` declares it.`);
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
const path = CONCEPT_PATHS[resolved.expectedKey];
|
|
1997
|
+
if (!path) return;
|
|
1998
|
+
const push = createPush();
|
|
1999
|
+
try {
|
|
2000
|
+
if (!await push(path, {
|
|
2001
|
+
source: pushSourceOf(resolved.source),
|
|
2002
|
+
rows: [row],
|
|
2003
|
+
mode: "delta",
|
|
2004
|
+
...resolved.expectedKey === "custom" ? { objectType: resolved.source.objectType } : {}
|
|
2005
|
+
})) console.warn(`Clivly instant push failed for "${entityKey}". This row will still sync on the next delta run.`);
|
|
2006
|
+
} catch (error) {
|
|
2007
|
+
console.warn(`Clivly instant push failed for "${entityKey}": ${error instanceof Error ? error.message : String(error)}. This row will still sync on the next delta run.`);
|
|
2008
|
+
}
|
|
2009
|
+
};
|
|
1974
2010
|
const describeSyncTrigger = () => ({
|
|
1975
2011
|
configured: config.syncTrigger?.url,
|
|
1976
2012
|
resolved: resolveTriggerUrl({
|
|
@@ -2326,7 +2362,8 @@ function createClivlySDK(config) {
|
|
|
2326
2362
|
runScheduled,
|
|
2327
2363
|
createClivlyHandler,
|
|
2328
2364
|
sync: runSync,
|
|
2329
|
-
createAuthVerifyHandler
|
|
2365
|
+
createAuthVerifyHandler,
|
|
2366
|
+
pushOne
|
|
2330
2367
|
};
|
|
2331
2368
|
}
|
|
2332
2369
|
//#endregion
|
|
@@ -208,6 +208,23 @@ interface ClivlySDKConfig {
|
|
|
208
208
|
*/
|
|
209
209
|
intervalMs?: number;
|
|
210
210
|
batchSize?: number;
|
|
211
|
+
/**
|
|
212
|
+
* Opt into `pushOne()`/`insertAndPush()` pushing a single changed row to
|
|
213
|
+
* Clivly immediately, instead of waiting for the next delta sync. Off by
|
|
214
|
+
* default: turning it on changes a write's async failure surface (a push
|
|
215
|
+
* can fail independently of the write that triggered it, swallowed and
|
|
216
|
+
* left for the next delta sync to catch), which an existing install
|
|
217
|
+
* upgrading to a newer `clivly` should opt into deliberately rather than
|
|
218
|
+
* inherit silently. Requires a real scheduled delta sync running
|
|
219
|
+
* underneath as the safety net for anything the instant push misses (a
|
|
220
|
+
* failed push, a raw SQL write, a bulk import) — see
|
|
221
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md.
|
|
222
|
+
*
|
|
223
|
+
* Can also be disabled at runtime without a redeploy via the
|
|
224
|
+
* `CLIVLY_INSTANT_PUSH_DISABLED=true` environment variable — the fast
|
|
225
|
+
* rollback switch if this misbehaves in production.
|
|
226
|
+
*/
|
|
227
|
+
instant?: boolean;
|
|
211
228
|
};
|
|
212
229
|
/**
|
|
213
230
|
* The remote sync trigger: how Clivly Cloud asks this app to run a sync.
|
|
@@ -521,6 +538,31 @@ interface ClivlySDK {
|
|
|
521
538
|
* own state.
|
|
522
539
|
*/
|
|
523
540
|
ping(): Promise<ClivlyConnectResult>;
|
|
541
|
+
/**
|
|
542
|
+
* Push one row to Clivly immediately, instead of waiting for the next delta
|
|
543
|
+
* sync — the "instant" half of near-real-time sync (see
|
|
544
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md). No-ops unless
|
|
545
|
+
* `sync.instant` is set and `CLIVLY_INSTANT_PUSH_DISABLED` is unset.
|
|
546
|
+
*
|
|
547
|
+
* `entityKey` is a key of `config.entities.entities` (the same key
|
|
548
|
+
* `clivly.config.ts` declares the entity under), not a table name — an
|
|
549
|
+
* unresolvable key logs a warning and returns without throwing, the same
|
|
550
|
+
* fail-safe as every other call site here: a config mismatch never syncs
|
|
551
|
+
* the wrong thing, it just doesn't sync this row instantly (the safety-net
|
|
552
|
+
* delta sync still will).
|
|
553
|
+
*
|
|
554
|
+
* `row` must be keyed by the source table's real database column names
|
|
555
|
+
* (`full_name`, not `fullName`) — the same contract `fetchPage` rows
|
|
556
|
+
* satisfy. `insertAndPush` (from `clivly/drizzle`) performs this
|
|
557
|
+
* translation for you from a Drizzle table; calling `pushOne` directly with
|
|
558
|
+
* a raw Drizzle `.returning()` row will silently push empty fields.
|
|
559
|
+
*
|
|
560
|
+
* Never throws and never advances the delta cursor. Fire-and-forget by
|
|
561
|
+
* design: on Cloudflare Workers and similar runtimes, wrap the call in
|
|
562
|
+
* `ctx.waitUntil(...)` so it survives past the response, since nothing
|
|
563
|
+
* here blocks or awaits the caller's own write.
|
|
564
|
+
*/
|
|
565
|
+
pushOne(entityKey: string, row: Record<string, unknown>): Promise<void>;
|
|
524
566
|
/**
|
|
525
567
|
* One-shot heartbeat + a single sync, with no keep-alive timers. The correct
|
|
526
568
|
* entry point on serverless/edge (Workers, Vercel, Lambda), where `start()`'s
|
|
@@ -208,6 +208,23 @@ interface ClivlySDKConfig {
|
|
|
208
208
|
*/
|
|
209
209
|
intervalMs?: number;
|
|
210
210
|
batchSize?: number;
|
|
211
|
+
/**
|
|
212
|
+
* Opt into `pushOne()`/`insertAndPush()` pushing a single changed row to
|
|
213
|
+
* Clivly immediately, instead of waiting for the next delta sync. Off by
|
|
214
|
+
* default: turning it on changes a write's async failure surface (a push
|
|
215
|
+
* can fail independently of the write that triggered it, swallowed and
|
|
216
|
+
* left for the next delta sync to catch), which an existing install
|
|
217
|
+
* upgrading to a newer `clivly` should opt into deliberately rather than
|
|
218
|
+
* inherit silently. Requires a real scheduled delta sync running
|
|
219
|
+
* underneath as the safety net for anything the instant push misses (a
|
|
220
|
+
* failed push, a raw SQL write, a bulk import) — see
|
|
221
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md.
|
|
222
|
+
*
|
|
223
|
+
* Can also be disabled at runtime without a redeploy via the
|
|
224
|
+
* `CLIVLY_INSTANT_PUSH_DISABLED=true` environment variable — the fast
|
|
225
|
+
* rollback switch if this misbehaves in production.
|
|
226
|
+
*/
|
|
227
|
+
instant?: boolean;
|
|
211
228
|
};
|
|
212
229
|
/**
|
|
213
230
|
* The remote sync trigger: how Clivly Cloud asks this app to run a sync.
|
|
@@ -521,6 +538,31 @@ interface ClivlySDK {
|
|
|
521
538
|
* own state.
|
|
522
539
|
*/
|
|
523
540
|
ping(): Promise<ClivlyConnectResult>;
|
|
541
|
+
/**
|
|
542
|
+
* Push one row to Clivly immediately, instead of waiting for the next delta
|
|
543
|
+
* sync — the "instant" half of near-real-time sync (see
|
|
544
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md). No-ops unless
|
|
545
|
+
* `sync.instant` is set and `CLIVLY_INSTANT_PUSH_DISABLED` is unset.
|
|
546
|
+
*
|
|
547
|
+
* `entityKey` is a key of `config.entities.entities` (the same key
|
|
548
|
+
* `clivly.config.ts` declares the entity under), not a table name — an
|
|
549
|
+
* unresolvable key logs a warning and returns without throwing, the same
|
|
550
|
+
* fail-safe as every other call site here: a config mismatch never syncs
|
|
551
|
+
* the wrong thing, it just doesn't sync this row instantly (the safety-net
|
|
552
|
+
* delta sync still will).
|
|
553
|
+
*
|
|
554
|
+
* `row` must be keyed by the source table's real database column names
|
|
555
|
+
* (`full_name`, not `fullName`) — the same contract `fetchPage` rows
|
|
556
|
+
* satisfy. `insertAndPush` (from `clivly/drizzle`) performs this
|
|
557
|
+
* translation for you from a Drizzle table; calling `pushOne` directly with
|
|
558
|
+
* a raw Drizzle `.returning()` row will silently push empty fields.
|
|
559
|
+
*
|
|
560
|
+
* Never throws and never advances the delta cursor. Fire-and-forget by
|
|
561
|
+
* design: on Cloudflare Workers and similar runtimes, wrap the call in
|
|
562
|
+
* `ctx.waitUntil(...)` so it survives past the response, since nothing
|
|
563
|
+
* here blocks or awaits the caller's own write.
|
|
564
|
+
*/
|
|
565
|
+
pushOne(entityKey: string, row: Record<string, unknown>): Promise<void>;
|
|
524
566
|
/**
|
|
525
567
|
* One-shot heartbeat + a single sync, with no keep-alive timers. The correct
|
|
526
568
|
* entry point on serverless/edge (Workers, Vercel, Lambda), where `start()`'s
|
package/dist/vite.d.cts
CHANGED
package/dist/vite.d.mts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clivly/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Clivly SDK — connect your app to Clivly Cloud (heartbeat, discovery, auth bridge).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"README.md"
|
|
54
54
|
],
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@clivly/core": "0.
|
|
56
|
+
"@clivly/core": "0.13.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@electric-sql/pglite": "^0.5.4",
|