clivly 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/drizzle.cjs +65 -0
- package/drizzle.d.cts +51 -1
- package/drizzle.d.mts +51 -1
- package/drizzle.mjs +64 -1
- package/index.cjs +1 -1
- package/index.mjs +1 -1
- package/{namespace-probe-CNQKSmxz.d.mts → namespace-probe-DykR0kgh.d.mts} +42 -0
- package/{namespace-probe-C5RLl74M.d.cts → namespace-probe-H7yWc0JV.d.cts} +42 -0
- package/package.json +1 -1
- package/{sdk-C-Pjxj2y.mjs → sdk-B-dp1tZN.mjs} +38 -1
- package/{sdk-BvGSxBQc.cjs → sdk-DKfKhJD_.cjs} +38 -1
- package/sdk.cjs +1 -1
- package/sdk.d.cts +1 -1
- package/sdk.d.mts +1 -1
- package/sdk.mjs +1 -1
- package/vite.d.cts +1 -1
- package/vite.d.mts +1 -1
package/drizzle.cjs
CHANGED
|
@@ -164,6 +164,69 @@ function fromDrizzle(db, table, opts) {
|
|
|
164
164
|
return source;
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
167
|
+
* Insert a row into a mapped Drizzle table and push it to Clivly immediately
|
|
168
|
+
* — the "one call does both" instant-push API (see
|
|
169
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md, question 1). Returns
|
|
170
|
+
* only after the write completes; the push itself is fire-and-forget (see
|
|
171
|
+
* `pushOne`), so this resolves at the same time a plain `db.insert(...)`
|
|
172
|
+
* would.
|
|
173
|
+
*
|
|
174
|
+
* Created once per mapped table, mirroring `fromDrizzle`'s own convention —
|
|
175
|
+
* no per-call table-to-entity lookup, so a table two entities share (e.g.
|
|
176
|
+
* `user` for both `user` and `organizationOwner`) is never ambiguous:
|
|
177
|
+
* ```ts
|
|
178
|
+
* const insertUser = insertAndPush(clivly, db, schema.user, { entity: "user" });
|
|
179
|
+
* // in the signup handler:
|
|
180
|
+
* const created = await insertUser(newUserValues, c.executionCtx);
|
|
181
|
+
* ```
|
|
182
|
+
*
|
|
183
|
+
* `waitUntil`, when provided (Hono exposes it as `c.executionCtx.waitUntil`),
|
|
184
|
+
* extends the request's lifetime past the response so the push survives on
|
|
185
|
+
* Cloudflare Workers and similar runtimes, which otherwise kill background
|
|
186
|
+
* work the instant a response is sent. Omit it on a long-lived process where
|
|
187
|
+
* nothing tears the request down early.
|
|
188
|
+
*
|
|
189
|
+
* Table property keys are translated to real database column names before
|
|
190
|
+
* the push — the same translation `fromDrizzle` performs on `fetchPage` rows
|
|
191
|
+
* — because a raw `.returning()` result is keyed by Drizzle property name
|
|
192
|
+
* (`fullName`), and every consumer downstream of a push (the fieldMap, the
|
|
193
|
+
* cloud's ingest) addresses columns by their database name (`full_name`).
|
|
194
|
+
* Skipping this would silently push empty fields with no error anywhere.
|
|
195
|
+
*/
|
|
196
|
+
/**
|
|
197
|
+
* Translate a Drizzle-property-keyed row (e.g. a `.returning()` result, or a
|
|
198
|
+
* plain unprojected `select()`) to real database column names — the same
|
|
199
|
+
* translation `fromDrizzle` performs on `fetchPage` rows. Every consumer
|
|
200
|
+
* downstream of a push (the fieldMap, the cloud's ingest) addresses columns
|
|
201
|
+
* by their database name (`full_name`), not the property name (`fullName`);
|
|
202
|
+
* skipping this would silently push empty fields with no error anywhere.
|
|
203
|
+
*
|
|
204
|
+
* Exported so a host can build the same translated row from a table it read
|
|
205
|
+
* some other way — e.g. re-querying a row inside an auth library's own
|
|
206
|
+
* lifecycle hook, where there is no `insertAndPush` call site to wrap
|
|
207
|
+
* because the library performs the insert internally.
|
|
208
|
+
*/
|
|
209
|
+
function toColumnKeyedRow(table, row) {
|
|
210
|
+
const columns = (0, drizzle_orm.getTableColumns)(table);
|
|
211
|
+
const columnNameByPropertyKey = /* @__PURE__ */ new Map();
|
|
212
|
+
for (const [propertyKey, column] of Object.entries(columns)) columnNameByPropertyKey.set(propertyKey, column.name);
|
|
213
|
+
const out = {};
|
|
214
|
+
for (const [propertyKey, value] of Object.entries(row)) out[columnNameByPropertyKey.get(propertyKey) ?? propertyKey] = value;
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
function insertAndPush(clivly, db, table, opts) {
|
|
218
|
+
const insertableDb = db;
|
|
219
|
+
return async (values, waitUntil) => {
|
|
220
|
+
const [created] = await insertableDb.insert(table).values(values).returning();
|
|
221
|
+
if (!created) throw new Error(`insertAndPush: insert into "${(0, drizzle_orm.getTableName)(table)}" returned no row.`);
|
|
222
|
+
const row = toColumnKeyedRow(table, created);
|
|
223
|
+
const pushed = clivly.pushOne(opts.entity, row);
|
|
224
|
+
if (waitUntil) waitUntil(pushed);
|
|
225
|
+
else await pushed;
|
|
226
|
+
return created;
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
167
230
|
* Build a `NamespaceIntrospector` from any Drizzle client that exposes
|
|
168
231
|
* `.execute()`. Reads `information_schema` in the connection's default schema
|
|
169
232
|
* (`current_schema()`), so it reports exactly the tables the app can reach.
|
|
@@ -198,3 +261,5 @@ exports.discoverFromDrizzle = require_drizzle.discoverFromDrizzle;
|
|
|
198
261
|
exports.drizzleIntrospector = drizzleIntrospector;
|
|
199
262
|
exports.fromDrizzle = fromDrizzle;
|
|
200
263
|
exports.fromDrizzleProjection = fromDrizzleProjection;
|
|
264
|
+
exports.insertAndPush = insertAndPush;
|
|
265
|
+
exports.toColumnKeyedRow = toColumnKeyedRow;
|
package/drizzle.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { R as ProjectionMapping, z as ProjectionRef } from "./entity-config-ApnT5j3X.cjs";
|
|
2
2
|
import { n as buildUniqueConstraints, r as discoverFromDrizzle } from "./drizzle-CiK37zJm.cjs";
|
|
3
|
-
import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-
|
|
3
|
+
import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-H7yWc0JV.cjs";
|
|
4
4
|
import { Table } from "drizzle-orm";
|
|
5
5
|
//#region ../sdk/src/drizzle-projection.d.ts
|
|
6
6
|
/**
|
|
@@ -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/drizzle.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { R as ProjectionMapping, z as ProjectionRef } from "./entity-config-CKcj4-M_.mjs";
|
|
2
2
|
import { n as buildUniqueConstraints, r as discoverFromDrizzle } from "./drizzle-ehVWLSrN.mjs";
|
|
3
|
-
import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-
|
|
3
|
+
import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-DykR0kgh.mjs";
|
|
4
4
|
import { Table } from "drizzle-orm";
|
|
5
5
|
//#region ../sdk/src/drizzle-projection.d.ts
|
|
6
6
|
/**
|
|
@@ -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/drizzle.mjs
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 = 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 "${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.
|
|
@@ -192,4 +255,4 @@ function drizzleIntrospector(db) {
|
|
|
192
255
|
};
|
|
193
256
|
}
|
|
194
257
|
//#endregion
|
|
195
|
-
export { buildUniqueConstraints, discoverFromDrizzle, drizzleIntrospector, fromDrizzle, fromDrizzleProjection };
|
|
258
|
+
export { buildUniqueConstraints, discoverFromDrizzle, drizzleIntrospector, fromDrizzle, fromDrizzleProjection, insertAndPush, toColumnKeyedRow };
|
package/index.cjs
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const require_entity_config = require("./entity-config-BI4RYNKL.cjs");
|
|
4
4
|
require("./mapping-score-CaB3F4Uu.cjs");
|
|
5
5
|
const require_internal_entity_heuristics = require("./internal-entity-heuristics.cjs");
|
|
6
|
-
const require_sdk = require("./sdk-
|
|
6
|
+
const require_sdk = require("./sdk-DKfKhJD_.cjs");
|
|
7
7
|
let node_child_process = require("node:child_process");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
let node_module = require("node:module");
|
package/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { t as CANONICAL_FIELDS } from "./entity-config-Ccfb2IL9.mjs";
|
|
3
3
|
import "./mapping-score-C68uAqGB.mjs";
|
|
4
4
|
import { hasDefiningColumn, isFrameworkTable, matchesConcept, requiredFieldsFor, scoreFieldMap, scoreRelationships, suggestCursorField, toFieldMap } from "./internal-entity-heuristics.mjs";
|
|
5
|
-
import { s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-
|
|
5
|
+
import { s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-B-dp1tZN.mjs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, watch, writeFileSync } from "node:fs";
|
|
@@ -164,6 +164,23 @@ interface ClivlySDKConfig {
|
|
|
164
164
|
*/
|
|
165
165
|
intervalMs?: number;
|
|
166
166
|
batchSize?: number;
|
|
167
|
+
/**
|
|
168
|
+
* Opt into `pushOne()`/`insertAndPush()` pushing a single changed row to
|
|
169
|
+
* Clivly immediately, instead of waiting for the next delta sync. Off by
|
|
170
|
+
* default: turning it on changes a write's async failure surface (a push
|
|
171
|
+
* can fail independently of the write that triggered it, swallowed and
|
|
172
|
+
* left for the next delta sync to catch), which an existing install
|
|
173
|
+
* upgrading to a newer `clivly` should opt into deliberately rather than
|
|
174
|
+
* inherit silently. Requires a real scheduled delta sync running
|
|
175
|
+
* underneath as the safety net for anything the instant push misses (a
|
|
176
|
+
* failed push, a raw SQL write, a bulk import) — see
|
|
177
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md.
|
|
178
|
+
*
|
|
179
|
+
* Can also be disabled at runtime without a redeploy via the
|
|
180
|
+
* `CLIVLY_INSTANT_PUSH_DISABLED=true` environment variable — the fast
|
|
181
|
+
* rollback switch if this misbehaves in production.
|
|
182
|
+
*/
|
|
183
|
+
instant?: boolean;
|
|
167
184
|
};
|
|
168
185
|
/**
|
|
169
186
|
* The remote sync trigger: how Clivly Cloud asks this app to run a sync.
|
|
@@ -477,6 +494,31 @@ interface ClivlySDK {
|
|
|
477
494
|
* own state.
|
|
478
495
|
*/
|
|
479
496
|
ping(): Promise<ClivlyConnectResult>;
|
|
497
|
+
/**
|
|
498
|
+
* Push one row to Clivly immediately, instead of waiting for the next delta
|
|
499
|
+
* sync — the "instant" half of near-real-time sync (see
|
|
500
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md). No-ops unless
|
|
501
|
+
* `sync.instant` is set and `CLIVLY_INSTANT_PUSH_DISABLED` is unset.
|
|
502
|
+
*
|
|
503
|
+
* `entityKey` is a key of `config.entities.entities` (the same key
|
|
504
|
+
* `clivly.config.ts` declares the entity under), not a table name — an
|
|
505
|
+
* unresolvable key logs a warning and returns without throwing, the same
|
|
506
|
+
* fail-safe as every other call site here: a config mismatch never syncs
|
|
507
|
+
* the wrong thing, it just doesn't sync this row instantly (the safety-net
|
|
508
|
+
* delta sync still will).
|
|
509
|
+
*
|
|
510
|
+
* `row` must be keyed by the source table's real database column names
|
|
511
|
+
* (`full_name`, not `fullName`) — the same contract `fetchPage` rows
|
|
512
|
+
* satisfy. `insertAndPush` (from `clivly/drizzle`) performs this
|
|
513
|
+
* translation for you from a Drizzle table; calling `pushOne` directly with
|
|
514
|
+
* a raw Drizzle `.returning()` row will silently push empty fields.
|
|
515
|
+
*
|
|
516
|
+
* Never throws and never advances the delta cursor. Fire-and-forget by
|
|
517
|
+
* design: on Cloudflare Workers and similar runtimes, wrap the call in
|
|
518
|
+
* `ctx.waitUntil(...)` so it survives past the response, since nothing
|
|
519
|
+
* here blocks or awaits the caller's own write.
|
|
520
|
+
*/
|
|
521
|
+
pushOne(entityKey: string, row: Record<string, unknown>): Promise<void>;
|
|
480
522
|
/**
|
|
481
523
|
* One-shot heartbeat + a single sync, with no keep-alive timers. The correct
|
|
482
524
|
* entry point on serverless/edge (Workers, Vercel, Lambda), where `start()`'s
|
|
@@ -164,6 +164,23 @@ interface ClivlySDKConfig {
|
|
|
164
164
|
*/
|
|
165
165
|
intervalMs?: number;
|
|
166
166
|
batchSize?: number;
|
|
167
|
+
/**
|
|
168
|
+
* Opt into `pushOne()`/`insertAndPush()` pushing a single changed row to
|
|
169
|
+
* Clivly immediately, instead of waiting for the next delta sync. Off by
|
|
170
|
+
* default: turning it on changes a write's async failure surface (a push
|
|
171
|
+
* can fail independently of the write that triggered it, swallowed and
|
|
172
|
+
* left for the next delta sync to catch), which an existing install
|
|
173
|
+
* upgrading to a newer `clivly` should opt into deliberately rather than
|
|
174
|
+
* inherit silently. Requires a real scheduled delta sync running
|
|
175
|
+
* underneath as the safety net for anything the instant push misses (a
|
|
176
|
+
* failed push, a raw SQL write, a bulk import) — see
|
|
177
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md.
|
|
178
|
+
*
|
|
179
|
+
* Can also be disabled at runtime without a redeploy via the
|
|
180
|
+
* `CLIVLY_INSTANT_PUSH_DISABLED=true` environment variable — the fast
|
|
181
|
+
* rollback switch if this misbehaves in production.
|
|
182
|
+
*/
|
|
183
|
+
instant?: boolean;
|
|
167
184
|
};
|
|
168
185
|
/**
|
|
169
186
|
* The remote sync trigger: how Clivly Cloud asks this app to run a sync.
|
|
@@ -477,6 +494,31 @@ interface ClivlySDK {
|
|
|
477
494
|
* own state.
|
|
478
495
|
*/
|
|
479
496
|
ping(): Promise<ClivlyConnectResult>;
|
|
497
|
+
/**
|
|
498
|
+
* Push one row to Clivly immediately, instead of waiting for the next delta
|
|
499
|
+
* sync — the "instant" half of near-real-time sync (see
|
|
500
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md). No-ops unless
|
|
501
|
+
* `sync.instant` is set and `CLIVLY_INSTANT_PUSH_DISABLED` is unset.
|
|
502
|
+
*
|
|
503
|
+
* `entityKey` is a key of `config.entities.entities` (the same key
|
|
504
|
+
* `clivly.config.ts` declares the entity under), not a table name — an
|
|
505
|
+
* unresolvable key logs a warning and returns without throwing, the same
|
|
506
|
+
* fail-safe as every other call site here: a config mismatch never syncs
|
|
507
|
+
* the wrong thing, it just doesn't sync this row instantly (the safety-net
|
|
508
|
+
* delta sync still will).
|
|
509
|
+
*
|
|
510
|
+
* `row` must be keyed by the source table's real database column names
|
|
511
|
+
* (`full_name`, not `fullName`) — the same contract `fetchPage` rows
|
|
512
|
+
* satisfy. `insertAndPush` (from `clivly/drizzle`) performs this
|
|
513
|
+
* translation for you from a Drizzle table; calling `pushOne` directly with
|
|
514
|
+
* a raw Drizzle `.returning()` row will silently push empty fields.
|
|
515
|
+
*
|
|
516
|
+
* Never throws and never advances the delta cursor. Fire-and-forget by
|
|
517
|
+
* design: on Cloudflare Workers and similar runtimes, wrap the call in
|
|
518
|
+
* `ctx.waitUntil(...)` so it survives past the response, since nothing
|
|
519
|
+
* here blocks or awaits the caller's own write.
|
|
520
|
+
*/
|
|
521
|
+
pushOne(entityKey: string, row: Record<string, unknown>): Promise<void>;
|
|
480
522
|
/**
|
|
481
523
|
* One-shot heartbeat + a single sync, with no keep-alive timers. The correct
|
|
482
524
|
* entry point on serverless/edge (Workers, Vercel, Lambda), where `start()`'s
|
package/package.json
CHANGED
|
@@ -699,6 +699,20 @@ function detectHostEnvironment(env, configured) {
|
|
|
699
699
|
return fromNodeEnv(env.NODE_ENV?.trim());
|
|
700
700
|
}
|
|
701
701
|
//#endregion
|
|
702
|
+
//#region ../sdk/src/instant-push-support.ts
|
|
703
|
+
/**
|
|
704
|
+
* The fast rollback switch for instant push (see
|
|
705
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md, question 5).
|
|
706
|
+
*
|
|
707
|
+
* A host-side env var, not a Clivly-Cloud-side toggle: it must keep working
|
|
708
|
+
* even when Clivly Cloud is the thing misbehaving (e.g. its ingest endpoint
|
|
709
|
+
* is what instant push is flooding), and it must take effect without a
|
|
710
|
+
* redeploy — just an env var change and the next cold start.
|
|
711
|
+
*/
|
|
712
|
+
function isInstantPushDisabled(env) {
|
|
713
|
+
return env.CLIVLY_INSTANT_PUSH_DISABLED === "true";
|
|
714
|
+
}
|
|
715
|
+
//#endregion
|
|
702
716
|
//#region ../sdk/src/presence-machine.ts
|
|
703
717
|
const PRESENCE_BACKOFF_BASE_MS = 1e3;
|
|
704
718
|
const PRESENCE_BACKOFF_MAX_MS = 3e4;
|
|
@@ -2090,6 +2104,28 @@ function createClivlySDK(config) {
|
|
|
2090
2104
|
lastPushFailure = null;
|
|
2091
2105
|
if (!await syncOne(resolved.source, resolved.path, "delta", resolved.objectType, runId, SAMPLE_BATCH_SIZE, SAMPLE_BATCH_SIZE, false)) throw pushFailureError(describePushFailure([resolved.source.entity]));
|
|
2092
2106
|
};
|
|
2107
|
+
const pushOne = async (entityKey, row) => {
|
|
2108
|
+
if (config.sync?.instant !== true) return;
|
|
2109
|
+
if (isInstantPushDisabled(config.env ?? process.env)) return;
|
|
2110
|
+
const resolved = resolveEntitySource(config, entityKey);
|
|
2111
|
+
if (!resolved) {
|
|
2112
|
+
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.`);
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
const path = CONCEPT_PATHS[resolved.expectedKey];
|
|
2116
|
+
if (!path) return;
|
|
2117
|
+
const push = createPush();
|
|
2118
|
+
try {
|
|
2119
|
+
if (!await push(path, {
|
|
2120
|
+
source: pushSourceOf(resolved.source),
|
|
2121
|
+
rows: [row],
|
|
2122
|
+
mode: "delta",
|
|
2123
|
+
...resolved.expectedKey === "custom" ? { objectType: resolved.source.objectType } : {}
|
|
2124
|
+
})) console.warn(`Clivly instant push failed for "${entityKey}". This row will still sync on the next delta run.`);
|
|
2125
|
+
} catch (error) {
|
|
2126
|
+
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.`);
|
|
2127
|
+
}
|
|
2128
|
+
};
|
|
2093
2129
|
const describeSyncTrigger = () => ({
|
|
2094
2130
|
configured: config.syncTrigger?.url,
|
|
2095
2131
|
resolved: resolveTriggerUrl({
|
|
@@ -2445,7 +2481,8 @@ function createClivlySDK(config) {
|
|
|
2445
2481
|
runScheduled,
|
|
2446
2482
|
createClivlyHandler,
|
|
2447
2483
|
sync: runSync,
|
|
2448
|
-
createAuthVerifyHandler
|
|
2484
|
+
createAuthVerifyHandler,
|
|
2485
|
+
pushOne
|
|
2449
2486
|
};
|
|
2450
2487
|
}
|
|
2451
2488
|
//#endregion
|
|
@@ -699,6 +699,20 @@ function detectHostEnvironment(env, configured) {
|
|
|
699
699
|
return fromNodeEnv(env.NODE_ENV?.trim());
|
|
700
700
|
}
|
|
701
701
|
//#endregion
|
|
702
|
+
//#region ../sdk/src/instant-push-support.ts
|
|
703
|
+
/**
|
|
704
|
+
* The fast rollback switch for instant push (see
|
|
705
|
+
* docs/reviews/2026-09-19-near-real-time-sync-plan.md, question 5).
|
|
706
|
+
*
|
|
707
|
+
* A host-side env var, not a Clivly-Cloud-side toggle: it must keep working
|
|
708
|
+
* even when Clivly Cloud is the thing misbehaving (e.g. its ingest endpoint
|
|
709
|
+
* is what instant push is flooding), and it must take effect without a
|
|
710
|
+
* redeploy — just an env var change and the next cold start.
|
|
711
|
+
*/
|
|
712
|
+
function isInstantPushDisabled(env) {
|
|
713
|
+
return env.CLIVLY_INSTANT_PUSH_DISABLED === "true";
|
|
714
|
+
}
|
|
715
|
+
//#endregion
|
|
702
716
|
//#region ../sdk/src/presence-machine.ts
|
|
703
717
|
const PRESENCE_BACKOFF_BASE_MS = 1e3;
|
|
704
718
|
const PRESENCE_BACKOFF_MAX_MS = 3e4;
|
|
@@ -2090,6 +2104,28 @@ function createClivlySDK(config) {
|
|
|
2090
2104
|
lastPushFailure = null;
|
|
2091
2105
|
if (!await syncOne(resolved.source, resolved.path, "delta", resolved.objectType, runId, SAMPLE_BATCH_SIZE, SAMPLE_BATCH_SIZE, false)) throw pushFailureError(describePushFailure([resolved.source.entity]));
|
|
2092
2106
|
};
|
|
2107
|
+
const pushOne = async (entityKey, row) => {
|
|
2108
|
+
if (config.sync?.instant !== true) return;
|
|
2109
|
+
if (isInstantPushDisabled(config.env ?? process.env)) return;
|
|
2110
|
+
const resolved = resolveEntitySource(config, entityKey);
|
|
2111
|
+
if (!resolved) {
|
|
2112
|
+
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.`);
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
const path = CONCEPT_PATHS[resolved.expectedKey];
|
|
2116
|
+
if (!path) return;
|
|
2117
|
+
const push = createPush();
|
|
2118
|
+
try {
|
|
2119
|
+
if (!await push(path, {
|
|
2120
|
+
source: pushSourceOf(resolved.source),
|
|
2121
|
+
rows: [row],
|
|
2122
|
+
mode: "delta",
|
|
2123
|
+
...resolved.expectedKey === "custom" ? { objectType: resolved.source.objectType } : {}
|
|
2124
|
+
})) console.warn(`Clivly instant push failed for "${entityKey}". This row will still sync on the next delta run.`);
|
|
2125
|
+
} catch (error) {
|
|
2126
|
+
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.`);
|
|
2127
|
+
}
|
|
2128
|
+
};
|
|
2093
2129
|
const describeSyncTrigger = () => ({
|
|
2094
2130
|
configured: config.syncTrigger?.url,
|
|
2095
2131
|
resolved: resolveTriggerUrl({
|
|
@@ -2445,7 +2481,8 @@ function createClivlySDK(config) {
|
|
|
2445
2481
|
runScheduled,
|
|
2446
2482
|
createClivlyHandler,
|
|
2447
2483
|
sync: runSync,
|
|
2448
|
-
createAuthVerifyHandler
|
|
2484
|
+
createAuthVerifyHandler,
|
|
2485
|
+
pushOne
|
|
2449
2486
|
};
|
|
2450
2487
|
}
|
|
2451
2488
|
//#endregion
|
package/sdk.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_entity_config = require("./entity-config-BI4RYNKL.cjs");
|
|
3
|
-
const require_sdk = require("./sdk-
|
|
3
|
+
const require_sdk = require("./sdk-DKfKhJD_.cjs");
|
|
4
4
|
exports.ClivlyAuthError = require_sdk.ClivlyAuthError;
|
|
5
5
|
exports.ClivlyError = require_sdk.ClivlyError;
|
|
6
6
|
exports.ClivlyNetworkError = require_sdk.ClivlyNetworkError;
|
package/sdk.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { C as customObjectDeclarations, M as validateEntitiesConfig, a as ClivlyEntitiesConfig, f as CustomObjectDeclaration, l as ConfigValidationError, m as DiscoveredSchemaTable, u as ConfigValidationResult, w as defineClivlyConfig } from "./entity-config-ApnT5j3X.cjs";
|
|
2
|
-
import { _ as RemoteProjectionsDescription, a as ClivlyConnectResult, c as ClivlyUser, d as DiscoveredTable, f as DoctorCheck, g as EnsuredObjectTypeResult, h as EnsuredMappingResult, i as ClivlyConnectReason, l as CreateChatSessionHandlerOptions, m as EnsureSchemaResult, n as ChatSessionRequestBody, o as ClivlySDK, p as DoctorReport, r as ChatWidgetVisitor, s as ClivlySDKConfig, u as CursorStore, y as SyncTriggerDescription } from "./namespace-probe-
|
|
2
|
+
import { _ as RemoteProjectionsDescription, a as ClivlyConnectResult, c as ClivlyUser, d as DiscoveredTable, f as DoctorCheck, g as EnsuredObjectTypeResult, h as EnsuredMappingResult, i as ClivlyConnectReason, l as CreateChatSessionHandlerOptions, m as EnsureSchemaResult, n as ChatSessionRequestBody, o as ClivlySDK, p as DoctorReport, r as ChatWidgetVisitor, s as ClivlySDKConfig, u as CursorStore, y as SyncTriggerDescription } from "./namespace-probe-H7yWc0JV.cjs";
|
|
3
3
|
//#region ../sdk/src/trigger-url.d.ts
|
|
4
4
|
export declare function isPlaceholderUrl(value: string): boolean;
|
|
5
5
|
//#endregion
|
package/sdk.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { C as customObjectDeclarations, M as validateEntitiesConfig, a as ClivlyEntitiesConfig, f as CustomObjectDeclaration, l as ConfigValidationError, m as DiscoveredSchemaTable, u as ConfigValidationResult, w as defineClivlyConfig } from "./entity-config-CKcj4-M_.mjs";
|
|
2
|
-
import { _ as RemoteProjectionsDescription, a as ClivlyConnectResult, c as ClivlyUser, d as DiscoveredTable, f as DoctorCheck, g as EnsuredObjectTypeResult, h as EnsuredMappingResult, i as ClivlyConnectReason, l as CreateChatSessionHandlerOptions, m as EnsureSchemaResult, n as ChatSessionRequestBody, o as ClivlySDK, p as DoctorReport, r as ChatWidgetVisitor, s as ClivlySDKConfig, u as CursorStore, y as SyncTriggerDescription } from "./namespace-probe-
|
|
2
|
+
import { _ as RemoteProjectionsDescription, a as ClivlyConnectResult, c as ClivlyUser, d as DiscoveredTable, f as DoctorCheck, g as EnsuredObjectTypeResult, h as EnsuredMappingResult, i as ClivlyConnectReason, l as CreateChatSessionHandlerOptions, m as EnsureSchemaResult, n as ChatSessionRequestBody, o as ClivlySDK, p as DoctorReport, r as ChatWidgetVisitor, s as ClivlySDKConfig, u as CursorStore, y as SyncTriggerDescription } from "./namespace-probe-DykR0kgh.mjs";
|
|
3
3
|
//#region ../sdk/src/trigger-url.d.ts
|
|
4
4
|
export declare function isPlaceholderUrl(value: string): boolean;
|
|
5
5
|
//#endregion
|
package/sdk.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { c as defineClivlyConfig, g as validateEntitiesConfig, s as customObjectDeclarations } from "./entity-config-Ccfb2IL9.mjs";
|
|
2
|
-
import { a as ClivlyRateLimitError, i as ClivlyNetworkError, n as ClivlyAuthError, o as createChatSessionHandler, r as ClivlyError, s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-
|
|
2
|
+
import { a as ClivlyRateLimitError, i as ClivlyNetworkError, n as ClivlyAuthError, o as createChatSessionHandler, r as ClivlyError, s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-B-dp1tZN.mjs";
|
|
3
3
|
export { ClivlyAuthError, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, createChatSessionHandler, createClivlySDK, customObjectDeclarations, defineClivlyConfig, isPlaceholderUrl, validateEntitiesConfig };
|
package/vite.d.cts
CHANGED
package/vite.d.mts
CHANGED