@electric-sql/client
Advanced tools
Comparing version 0.6.5 to 0.7.0
@@ -120,2 +120,3 @@ /** | ||
type Replica = `full` | `default`; | ||
/** | ||
@@ -126,7 +127,16 @@ * Options for constructing a ShapeStream. | ||
/** | ||
* The full URL to where the Shape is hosted. This can either be the Electric server | ||
* directly or a proxy. E.g. for a local Electric instance, you might set `http://localhost:3000/v1/shape/foo` | ||
* The full URL to where the Shape is served. This can either be the Electric server | ||
* directly or a proxy. E.g. for a local Electric instance, you might set `http://localhost:3000/v1/shape` | ||
*/ | ||
url: string; | ||
/** | ||
* Which database to use. | ||
* This is optional unless Electric is used with multiple databases. | ||
*/ | ||
databaseId?: string; | ||
/** | ||
* The root table for the shape. | ||
*/ | ||
table: string; | ||
/** | ||
* The where clauses for the shape. | ||
@@ -141,2 +151,13 @@ */ | ||
/** | ||
* If `replica` is `default` (the default) then Electric will only send the | ||
* changed columns in an update. | ||
* | ||
* If it's `full` Electric will send the entire row with both changed and | ||
* unchanged values. | ||
* | ||
* Setting `replica` to `full` will obviously result in higher bandwidth | ||
* usage and so is not recommended. | ||
*/ | ||
replica?: Replica; | ||
/** | ||
* The "offset" on the shape log. This is typically not set as the ShapeStream | ||
@@ -146,3 +167,3 @@ * will handle this automatically. A common scenario where you might pass an offset | ||
* and are re-starting a ShapeStream to catch-up to the latest state of the Shape, | ||
* you'd pass in the last offset and shapeId you'd seen from the Electric server | ||
* you'd pass in the last offset and shapeHandle you'd seen from the Electric server | ||
* so it knows at what point in the shape to catch you up from. | ||
@@ -155,3 +176,3 @@ */ | ||
*/ | ||
shapeId?: string; | ||
shapeHandle?: string; | ||
backoffOptions?: BackoffOptions; | ||
@@ -182,3 +203,3 @@ /** | ||
isUpToDate: boolean; | ||
shapeId?: string; | ||
shapeHandle?: string; | ||
} | ||
@@ -217,5 +238,9 @@ /** | ||
#private; | ||
static readonly Replica: { | ||
FULL: Replica; | ||
DEFAULT: Replica; | ||
}; | ||
readonly options: ShapeStreamOptions<GetExtensions<T>>; | ||
constructor(options: ShapeStreamOptions<GetExtensions<T>>); | ||
get shapeId(): string; | ||
get shapeHandle(): string; | ||
get isUpToDate(): boolean; | ||
@@ -239,6 +264,9 @@ get error(): unknown; | ||
type ShapeData<T extends Row<unknown> = Row> = Map<string, T>; | ||
type ShapeChangedCallback<T extends Row<unknown> = Row> = (value: ShapeData<T>) => void; | ||
type ShapeChangedCallback<T extends Row<unknown> = Row> = (data: { | ||
value: ShapeData<T>; | ||
rows: T[]; | ||
}) => void; | ||
/** | ||
* A Shape is an object that subscribes to a shape log, | ||
* keeps a materialised shape `.value` in memory and | ||
* keeps a materialised shape `.rows` in memory and | ||
* notifies subscribers when the value has changed. | ||
@@ -253,19 +281,19 @@ * | ||
* ``` | ||
* const shapeStream = new ShapeStream<{ foo: number }>(url: 'http://localhost:3000/v1/shape/foo'}) | ||
* const shapeStream = new ShapeStream<{ foo: number }>(url: `http://localhost:3000/v1/shape`, table: `foo`}) | ||
* const shape = new Shape(shapeStream) | ||
* ``` | ||
* | ||
* `value` returns a promise that resolves the Shape data once the Shape has been | ||
* `rows` returns a promise that resolves the Shape data once the Shape has been | ||
* fully loaded (and when resuming from being offline): | ||
* | ||
* const value = await shape.value | ||
* const rows = await shape.rows | ||
* | ||
* `valueSync` returns the current data synchronously: | ||
* `currentRows` returns the current data synchronously: | ||
* | ||
* const value = shape.valueSync | ||
* const rows = shape.currentRows | ||
* | ||
* Subscribe to updates. Called whenever the shape updates in Postgres. | ||
* | ||
* shape.subscribe(shapeData => { | ||
* console.log(shapeData) | ||
* shape.subscribe(({ rows }) => { | ||
* console.log(rows) | ||
* }) | ||
@@ -277,4 +305,6 @@ */ | ||
get isUpToDate(): boolean; | ||
get rows(): Promise<T[]>; | ||
get currentRows(): T[]; | ||
get value(): Promise<ShapeData<T>>; | ||
get valueSync(): ShapeData<T>; | ||
get currentValue(): ShapeData<T>; | ||
get error(): false | FetchError; | ||
@@ -281,0 +311,0 @@ /** Unix time at which we last synced. Undefined when `isLoading` is true. */ |
@@ -198,13 +198,16 @@ var __defProp = Object.defineProperty; | ||
// src/constants.ts | ||
var SHAPE_ID_HEADER = `electric-shape-id`; | ||
var LIVE_CACHE_BUSTER_HEADER = `electric-next-cursor`; | ||
var LIVE_CACHE_BUSTER_HEADER = `electric-cursor`; | ||
var SHAPE_HANDLE_HEADER = `electric-handle`; | ||
var CHUNK_LAST_OFFSET_HEADER = `electric-offset`; | ||
var SHAPE_SCHEMA_HEADER = `electric-schema`; | ||
var CHUNK_UP_TO_DATE_HEADER = `electric-up-to-date`; | ||
var DATABASE_ID_QUERY_PARAM = `database_id`; | ||
var COLUMNS_QUERY_PARAM = `columns`; | ||
var LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor`; | ||
var CHUNK_LAST_OFFSET_HEADER = `electric-chunk-last-offset`; | ||
var CHUNK_UP_TO_DATE_HEADER = `electric-chunk-up-to-date`; | ||
var SHAPE_SCHEMA_HEADER = `electric-schema`; | ||
var SHAPE_ID_QUERY_PARAM = `shape_id`; | ||
var SHAPE_HANDLE_QUERY_PARAM = `handle`; | ||
var LIVE_QUERY_PARAM = `live`; | ||
var OFFSET_QUERY_PARAM = `offset`; | ||
var TABLE_QUERY_PARAM = `table`; | ||
var WHERE_QUERY_PARAM = `where`; | ||
var COLUMNS_QUERY_PARAM = `columns`; | ||
var LIVE_QUERY_PARAM = `live`; | ||
var REPLICA_PARAM = `replica`; | ||
@@ -349,9 +352,9 @@ // src/fetch.ts | ||
function getNextChunkUrl(url, res) { | ||
const shapeId = res.headers.get(SHAPE_ID_HEADER); | ||
const shapeHandle = res.headers.get(SHAPE_HANDLE_HEADER); | ||
const lastOffset = res.headers.get(CHUNK_LAST_OFFSET_HEADER); | ||
const isUpToDate = res.headers.has(CHUNK_UP_TO_DATE_HEADER); | ||
if (!shapeId || !lastOffset || isUpToDate) return; | ||
if (!shapeHandle || !lastOffset || isUpToDate) return; | ||
const nextUrl = new URL(url); | ||
if (nextUrl.searchParams.has(LIVE_QUERY_PARAM)) return; | ||
nextUrl.searchParams.set(SHAPE_ID_QUERY_PARAM, shapeId); | ||
nextUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, shapeHandle); | ||
nextUrl.searchParams.set(OFFSET_QUERY_PARAM, lastOffset); | ||
@@ -371,4 +374,4 @@ return nextUrl.toString(); | ||
// src/client.ts | ||
var _fetchClient2, _messageParser, _subscribers, _upToDateSubscribers, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _connected, _shapeId, _schema, _error, _ShapeStream_instances, publish_fn, sendErrorToSubscribers_fn, notifyUpToDateSubscribers_fn, sendErrorToUpToDateSubscribers_fn, reset_fn; | ||
var ShapeStream = class { | ||
var _fetchClient2, _messageParser, _subscribers, _upToDateSubscribers, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _connected, _shapeHandle, _databaseId, _schema, _error, _replica, _ShapeStream_instances, publish_fn, sendErrorToSubscribers_fn, notifyUpToDateSubscribers_fn, sendErrorToUpToDateSubscribers_fn, reset_fn; | ||
var _ShapeStream = class _ShapeStream { | ||
constructor(options) { | ||
@@ -387,5 +390,7 @@ __privateAdd(this, _ShapeStream_instances); | ||
__privateAdd(this, _connected, false); | ||
__privateAdd(this, _shapeId); | ||
__privateAdd(this, _shapeHandle); | ||
__privateAdd(this, _databaseId); | ||
__privateAdd(this, _schema); | ||
__privateAdd(this, _error); | ||
__privateAdd(this, _replica); | ||
var _a, _b, _c; | ||
@@ -396,4 +401,6 @@ validateOptions(options); | ||
__privateSet(this, _liveCacheBuster, ``); | ||
__privateSet(this, _shapeId, this.options.shapeId); | ||
__privateSet(this, _shapeHandle, this.options.shapeHandle); | ||
__privateSet(this, _databaseId, this.options.databaseId); | ||
__privateSet(this, _messageParser, new MessageParser(options.parser)); | ||
__privateSet(this, _replica, this.options.replica); | ||
const baseFetchClient = (_b = options.fetchClient) != null ? _b : (...args) => fetch(...args); | ||
@@ -410,4 +417,4 @@ const fetchWithBackoffClient = createFetchWithBackoff(baseFetchClient, __spreadProps(__spreadValues({}, (_c = options.backoffOptions) != null ? _c : BackoffDefaults), { | ||
} | ||
get shapeId() { | ||
return __privateGet(this, _shapeId); | ||
get shapeHandle() { | ||
return __privateGet(this, _shapeHandle); | ||
} | ||
@@ -421,8 +428,9 @@ get isUpToDate() { | ||
async start() { | ||
var _a; | ||
var _a, _b; | ||
__privateSet(this, _isUpToDate, false); | ||
const { url, where, columns, signal } = this.options; | ||
const { url, table, where, columns, signal } = this.options; | ||
try { | ||
while (!(signal == null ? void 0 : signal.aborted) && !__privateGet(this, _isUpToDate) || this.options.subscribe) { | ||
const fetchUrl = new URL(url); | ||
fetchUrl.searchParams.set(TABLE_QUERY_PARAM, table); | ||
if (where) fetchUrl.searchParams.set(WHERE_QUERY_PARAM, where); | ||
@@ -439,5 +447,14 @@ if (columns && columns.length > 0) | ||
} | ||
if (__privateGet(this, _shapeId)) { | ||
fetchUrl.searchParams.set(SHAPE_ID_QUERY_PARAM, __privateGet(this, _shapeId)); | ||
if (__privateGet(this, _shapeHandle)) { | ||
fetchUrl.searchParams.set( | ||
SHAPE_HANDLE_QUERY_PARAM, | ||
__privateGet(this, _shapeHandle) | ||
); | ||
} | ||
if (__privateGet(this, _databaseId)) { | ||
fetchUrl.searchParams.set(DATABASE_ID_QUERY_PARAM, __privateGet(this, _databaseId)); | ||
} | ||
if (((_a = __privateGet(this, _replica)) != null ? _a : _ShapeStream.Replica.DEFAULT) != _ShapeStream.Replica.DEFAULT) { | ||
fetchUrl.searchParams.set(REPLICA_PARAM, __privateGet(this, _replica)); | ||
} | ||
let response; | ||
@@ -454,4 +471,4 @@ try { | ||
if (e.status == 409) { | ||
const newShapeId = e.headers[SHAPE_ID_HEADER]; | ||
__privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeId); | ||
const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER]; | ||
__privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle); | ||
await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, e.json); | ||
@@ -466,5 +483,5 @@ continue; | ||
const { headers, status } = response; | ||
const shapeId = headers.get(SHAPE_ID_HEADER); | ||
if (shapeId) { | ||
__privateSet(this, _shapeId, shapeId); | ||
const shapeHandle = headers.get(SHAPE_HANDLE_HEADER); | ||
if (shapeHandle) { | ||
__privateSet(this, _shapeHandle, shapeHandle); | ||
} | ||
@@ -483,3 +500,3 @@ const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER); | ||
}; | ||
__privateSet(this, _schema, (_a = __privateGet(this, _schema)) != null ? _a : getSchema()); | ||
__privateSet(this, _schema, (_b = __privateGet(this, _schema)) != null ? _b : getSchema()); | ||
const messages = status === 204 ? `[]` : await response.text(); | ||
@@ -556,5 +573,7 @@ if (status === 204) { | ||
_connected = new WeakMap(); | ||
_shapeId = new WeakMap(); | ||
_shapeHandle = new WeakMap(); | ||
_databaseId = new WeakMap(); | ||
_schema = new WeakMap(); | ||
_error = new WeakMap(); | ||
_replica = new WeakMap(); | ||
_ShapeStream_instances = new WeakSet(); | ||
@@ -591,8 +610,8 @@ publish_fn = async function(messages) { | ||
* Resets the state of the stream, optionally with a provided | ||
* shape ID | ||
* shape handle | ||
*/ | ||
reset_fn = function(shapeId) { | ||
reset_fn = function(shapeHandle) { | ||
__privateSet(this, _lastOffset, `-1`); | ||
__privateSet(this, _liveCacheBuster, ``); | ||
__privateSet(this, _shapeId, shapeId); | ||
__privateSet(this, _shapeHandle, shapeHandle); | ||
__privateSet(this, _isUpToDate, false); | ||
@@ -602,6 +621,14 @@ __privateSet(this, _connected, false); | ||
}; | ||
_ShapeStream.Replica = { | ||
FULL: `full`, | ||
DEFAULT: `default` | ||
}; | ||
var ShapeStream = _ShapeStream; | ||
function validateOptions(options) { | ||
if (!options.url) { | ||
throw new Error(`Invalid shape option. It must provide the url`); | ||
throw new Error(`Invalid shape options. It must provide the url`); | ||
} | ||
if (!options.table) { | ||
throw new Error(`Invalid shape options. It must provide the table`); | ||
} | ||
if (options.signal && !(options.signal instanceof AbortSignal)) { | ||
@@ -612,5 +639,5 @@ throw new Error( | ||
} | ||
if (options.offset !== void 0 && options.offset !== `-1` && !options.shapeId) { | ||
if (options.offset !== void 0 && options.offset !== `-1` && !options.shapeHandle) { | ||
throw new Error( | ||
`shapeId is required if this isn't an initial fetch (i.e. offset > -1)` | ||
`shapeHandle is required if this isn't an initial fetch (i.e. offset > -1)` | ||
); | ||
@@ -649,11 +676,17 @@ } | ||
} | ||
get rows() { | ||
return this.value.then((v) => Array.from(v.values())); | ||
} | ||
get currentRows() { | ||
return Array.from(this.currentValue.values()); | ||
} | ||
get value() { | ||
return new Promise((resolve, reject) => { | ||
if (__privateGet(this, _stream).isUpToDate) { | ||
resolve(this.valueSync); | ||
resolve(this.currentValue); | ||
} else { | ||
const unsubscribe = this.subscribe((shapeData) => { | ||
const unsubscribe = this.subscribe(({ value }) => { | ||
unsubscribe(); | ||
if (__privateGet(this, _error2)) reject(__privateGet(this, _error2)); | ||
resolve(shapeData); | ||
resolve(value); | ||
}); | ||
@@ -663,3 +696,3 @@ } | ||
} | ||
get valueSync() { | ||
get currentValue() { | ||
return __privateGet(this, _data); | ||
@@ -758,3 +791,3 @@ } | ||
__privateGet(this, _subscribers2).forEach((callback) => { | ||
callback(this.valueSync); | ||
callback({ value: this.currentValue, rows: this.currentRows }); | ||
}); | ||
@@ -761,0 +794,0 @@ }; |
{ | ||
"name": "@electric-sql/client", | ||
"version": "0.6.5", | ||
"version": "0.7.0", | ||
"description": "Postgres everywhere - your data, in sync, wherever you need it.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -53,3 +53,4 @@ <p align="center"> | ||
const stream = new ShapeStream({ | ||
url: `${BASE_URL}/v1/shape/foo`, | ||
url: `${BASE_URL}/v1/shape`, | ||
table: `foo`, | ||
}) | ||
@@ -70,3 +71,4 @@ | ||
const stream = new ShapeStream({ | ||
url: `${BASE_URL}/v1/shape/foo`, | ||
url: `${BASE_URL}/v1/shape`, | ||
table: `foo`, | ||
}) | ||
@@ -76,7 +78,7 @@ const shape = new Shape(stream) | ||
// Returns promise that resolves with the latest shape data once it's fully loaded | ||
await shape.value | ||
await shape.rows | ||
// passes subscribers shape data when the shape updates | ||
shape.subscribe(shapeData => { | ||
// shapeData is a Map of the latest value of each row in a shape. | ||
shape.subscribe(({ rows }) => { | ||
// rows is an array of the latest value of each row in a shape. | ||
} | ||
@@ -83,0 +85,0 @@ ``` |
@@ -25,8 +25,13 @@ import { | ||
OFFSET_QUERY_PARAM, | ||
SHAPE_ID_HEADER, | ||
SHAPE_ID_QUERY_PARAM, | ||
SHAPE_HANDLE_HEADER, | ||
SHAPE_HANDLE_QUERY_PARAM, | ||
SHAPE_SCHEMA_HEADER, | ||
WHERE_QUERY_PARAM, | ||
DATABASE_ID_QUERY_PARAM, | ||
TABLE_QUERY_PARAM, | ||
REPLICA_PARAM, | ||
} from './constants' | ||
type Replica = `full` | `default` | ||
/** | ||
@@ -37,7 +42,19 @@ * Options for constructing a ShapeStream. | ||
/** | ||
* The full URL to where the Shape is hosted. This can either be the Electric server | ||
* directly or a proxy. E.g. for a local Electric instance, you might set `http://localhost:3000/v1/shape/foo` | ||
* The full URL to where the Shape is served. This can either be the Electric server | ||
* directly or a proxy. E.g. for a local Electric instance, you might set `http://localhost:3000/v1/shape` | ||
*/ | ||
url: string | ||
/** | ||
* Which database to use. | ||
* This is optional unless Electric is used with multiple databases. | ||
*/ | ||
databaseId?: string | ||
/** | ||
* The root table for the shape. | ||
*/ | ||
table: string | ||
/** | ||
* The where clauses for the shape. | ||
@@ -54,2 +71,13 @@ */ | ||
/** | ||
* If `replica` is `default` (the default) then Electric will only send the | ||
* changed columns in an update. | ||
* | ||
* If it's `full` Electric will send the entire row with both changed and | ||
* unchanged values. | ||
* | ||
* Setting `replica` to `full` will obviously result in higher bandwidth | ||
* usage and so is not recommended. | ||
*/ | ||
replica?: Replica | ||
/** | ||
* The "offset" on the shape log. This is typically not set as the ShapeStream | ||
@@ -59,3 +87,3 @@ * will handle this automatically. A common scenario where you might pass an offset | ||
* and are re-starting a ShapeStream to catch-up to the latest state of the Shape, | ||
* you'd pass in the last offset and shapeId you'd seen from the Electric server | ||
* you'd pass in the last offset and shapeHandle you'd seen from the Electric server | ||
* so it knows at what point in the shape to catch you up from. | ||
@@ -68,3 +96,3 @@ */ | ||
*/ | ||
shapeId?: string | ||
shapeHandle?: string | ||
backoffOptions?: BackoffOptions | ||
@@ -106,3 +134,3 @@ | ||
isUpToDate: boolean | ||
shapeId?: string | ||
shapeHandle?: string | ||
} | ||
@@ -144,2 +172,7 @@ | ||
{ | ||
static readonly Replica = { | ||
FULL: `full` as Replica, | ||
DEFAULT: `default` as Replica, | ||
} | ||
readonly options: ShapeStreamOptions<GetExtensions<T>> | ||
@@ -167,5 +200,7 @@ | ||
#connected: boolean = false | ||
#shapeId?: string | ||
#shapeHandle?: string | ||
#databaseId?: string | ||
#schema?: Schema | ||
#error?: unknown | ||
#replica?: Replica | ||
@@ -177,4 +212,6 @@ constructor(options: ShapeStreamOptions<GetExtensions<T>>) { | ||
this.#liveCacheBuster = `` | ||
this.#shapeId = this.options.shapeId | ||
this.#shapeHandle = this.options.shapeHandle | ||
this.#databaseId = this.options.databaseId | ||
this.#messageParser = new MessageParser<T>(options.parser) | ||
this.#replica = this.options.replica | ||
@@ -198,4 +235,4 @@ const baseFetchClient = | ||
get shapeId() { | ||
return this.#shapeId | ||
get shapeHandle() { | ||
return this.#shapeHandle | ||
} | ||
@@ -214,3 +251,3 @@ | ||
const { url, where, columns, signal } = this.options | ||
const { url, table, where, columns, signal } = this.options | ||
@@ -223,2 +260,3 @@ try { | ||
const fetchUrl = new URL(url) | ||
fetchUrl.searchParams.set(TABLE_QUERY_PARAM, table) | ||
if (where) fetchUrl.searchParams.set(WHERE_QUERY_PARAM, where) | ||
@@ -237,7 +275,21 @@ if (columns && columns.length > 0) | ||
if (this.#shapeId) { | ||
if (this.#shapeHandle) { | ||
// This should probably be a header for better cache breaking? | ||
fetchUrl.searchParams.set(SHAPE_ID_QUERY_PARAM, this.#shapeId!) | ||
fetchUrl.searchParams.set( | ||
SHAPE_HANDLE_QUERY_PARAM, | ||
this.#shapeHandle! | ||
) | ||
} | ||
if (this.#databaseId) { | ||
fetchUrl.searchParams.set(DATABASE_ID_QUERY_PARAM, this.#databaseId!) | ||
} | ||
if ( | ||
(this.#replica ?? ShapeStream.Replica.DEFAULT) != | ||
ShapeStream.Replica.DEFAULT | ||
) { | ||
fetchUrl.searchParams.set(REPLICA_PARAM, this.#replica as string) | ||
} | ||
let response!: Response | ||
@@ -255,5 +307,5 @@ try { | ||
// Upon receiving a 409, we should start from scratch | ||
// with the newly provided shape ID | ||
const newShapeId = e.headers[SHAPE_ID_HEADER] | ||
this.#reset(newShapeId) | ||
// with the newly provided shape handle | ||
const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER] | ||
this.#reset(newShapeHandle) | ||
await this.#publish(e.json as Message<T>[]) | ||
@@ -273,5 +325,5 @@ continue | ||
const { headers, status } = response | ||
const shapeId = headers.get(SHAPE_ID_HEADER) | ||
if (shapeId) { | ||
this.#shapeId = shapeId | ||
const shapeHandle = headers.get(SHAPE_HANDLE_HEADER) | ||
if (shapeHandle) { | ||
this.#shapeHandle = shapeHandle | ||
} | ||
@@ -415,8 +467,8 @@ | ||
* Resets the state of the stream, optionally with a provided | ||
* shape ID | ||
* shape handle | ||
*/ | ||
#reset(shapeId?: string) { | ||
#reset(shapeHandle?: string) { | ||
this.#lastOffset = `-1` | ||
this.#liveCacheBuster = `` | ||
this.#shapeId = shapeId | ||
this.#shapeHandle = shapeHandle | ||
this.#isUpToDate = false | ||
@@ -430,4 +482,7 @@ this.#connected = false | ||
if (!options.url) { | ||
throw new Error(`Invalid shape option. It must provide the url`) | ||
throw new Error(`Invalid shape options. It must provide the url`) | ||
} | ||
if (!options.table) { | ||
throw new Error(`Invalid shape options. It must provide the table`) | ||
} | ||
if (options.signal && !(options.signal instanceof AbortSignal)) { | ||
@@ -442,6 +497,6 @@ throw new Error( | ||
options.offset !== `-1` && | ||
!options.shapeId | ||
!options.shapeHandle | ||
) { | ||
throw new Error( | ||
`shapeId is required if this isn't an initial fetch (i.e. offset > -1)` | ||
`shapeHandle is required if this isn't an initial fetch (i.e. offset > -1)` | ||
) | ||
@@ -448,0 +503,0 @@ } |
@@ -1,11 +0,14 @@ | ||
export const SHAPE_ID_HEADER = `electric-shape-id` | ||
export const LIVE_CACHE_BUSTER_HEADER = `electric-next-cursor` | ||
export const LIVE_CACHE_BUSTER_HEADER = `electric-cursor` | ||
export const SHAPE_HANDLE_HEADER = `electric-handle` | ||
export const CHUNK_LAST_OFFSET_HEADER = `electric-offset` | ||
export const SHAPE_SCHEMA_HEADER = `electric-schema` | ||
export const CHUNK_UP_TO_DATE_HEADER = `electric-up-to-date` | ||
export const DATABASE_ID_QUERY_PARAM = `database_id` | ||
export const COLUMNS_QUERY_PARAM = `columns` | ||
export const LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor` | ||
export const CHUNK_LAST_OFFSET_HEADER = `electric-chunk-last-offset` | ||
export const CHUNK_UP_TO_DATE_HEADER = `electric-chunk-up-to-date` | ||
export const SHAPE_SCHEMA_HEADER = `electric-schema` | ||
export const SHAPE_ID_QUERY_PARAM = `shape_id` | ||
export const SHAPE_HANDLE_QUERY_PARAM = `handle` | ||
export const LIVE_QUERY_PARAM = `live` | ||
export const OFFSET_QUERY_PARAM = `offset` | ||
export const TABLE_QUERY_PARAM = `table` | ||
export const WHERE_QUERY_PARAM = `where` | ||
export const COLUMNS_QUERY_PARAM = `columns` | ||
export const LIVE_QUERY_PARAM = `live` | ||
export const REPLICA_PARAM = `replica` |
@@ -6,4 +6,4 @@ import { | ||
OFFSET_QUERY_PARAM, | ||
SHAPE_ID_HEADER, | ||
SHAPE_ID_QUERY_PARAM, | ||
SHAPE_HANDLE_HEADER, | ||
SHAPE_HANDLE_QUERY_PARAM, | ||
} from './constants' | ||
@@ -249,9 +249,9 @@ import { FetchError, FetchBackoffAbortError } from './error' | ||
function getNextChunkUrl(url: string, res: Response): string | void { | ||
const shapeId = res.headers.get(SHAPE_ID_HEADER) | ||
const shapeHandle = res.headers.get(SHAPE_HANDLE_HEADER) | ||
const lastOffset = res.headers.get(CHUNK_LAST_OFFSET_HEADER) | ||
const isUpToDate = res.headers.has(CHUNK_UP_TO_DATE_HEADER) | ||
// only prefetch if shape ID and offset for next chunk are available, and | ||
// only prefetch if shape handle and offset for next chunk are available, and | ||
// response is not already up-to-date | ||
if (!shapeId || !lastOffset || isUpToDate) return | ||
if (!shapeHandle || !lastOffset || isUpToDate) return | ||
@@ -264,3 +264,3 @@ const nextUrl = new URL(url) | ||
nextUrl.searchParams.set(SHAPE_ID_QUERY_PARAM, shapeId) | ||
nextUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, shapeHandle) | ||
nextUrl.searchParams.set(OFFSET_QUERY_PARAM, lastOffset) | ||
@@ -277,3 +277,3 @@ return nextUrl.toString() | ||
aborter: AbortController, | ||
sourceSignal?: AbortSignal | ||
sourceSignal?: AbortSignal | null | ||
): AbortSignal { | ||
@@ -280,0 +280,0 @@ if (!sourceSignal) return aborter.signal |
@@ -7,9 +7,10 @@ import { Message, Row } from './types' | ||
export type ShapeData<T extends Row<unknown> = Row> = Map<string, T> | ||
export type ShapeChangedCallback<T extends Row<unknown> = Row> = ( | ||
export type ShapeChangedCallback<T extends Row<unknown> = Row> = (data: { | ||
value: ShapeData<T> | ||
) => void | ||
rows: T[] | ||
}) => void | ||
/** | ||
* A Shape is an object that subscribes to a shape log, | ||
* keeps a materialised shape `.value` in memory and | ||
* keeps a materialised shape `.rows` in memory and | ||
* notifies subscribers when the value has changed. | ||
@@ -24,19 +25,19 @@ * | ||
* ``` | ||
* const shapeStream = new ShapeStream<{ foo: number }>(url: 'http://localhost:3000/v1/shape/foo'}) | ||
* const shapeStream = new ShapeStream<{ foo: number }>(url: `http://localhost:3000/v1/shape`, table: `foo`}) | ||
* const shape = new Shape(shapeStream) | ||
* ``` | ||
* | ||
* `value` returns a promise that resolves the Shape data once the Shape has been | ||
* `rows` returns a promise that resolves the Shape data once the Shape has been | ||
* fully loaded (and when resuming from being offline): | ||
* | ||
* const value = await shape.value | ||
* const rows = await shape.rows | ||
* | ||
* `valueSync` returns the current data synchronously: | ||
* `currentRows` returns the current data synchronously: | ||
* | ||
* const value = shape.valueSync | ||
* const rows = shape.currentRows | ||
* | ||
* Subscribe to updates. Called whenever the shape updates in Postgres. | ||
* | ||
* shape.subscribe(shapeData => { | ||
* console.log(shapeData) | ||
* shape.subscribe(({ rows }) => { | ||
* console.log(rows) | ||
* }) | ||
@@ -74,11 +75,19 @@ */ | ||
get rows(): Promise<T[]> { | ||
return this.value.then((v) => Array.from(v.values())) | ||
} | ||
get currentRows(): T[] { | ||
return Array.from(this.currentValue.values()) | ||
} | ||
get value(): Promise<ShapeData<T>> { | ||
return new Promise((resolve, reject) => { | ||
if (this.#stream.isUpToDate) { | ||
resolve(this.valueSync) | ||
resolve(this.currentValue) | ||
} else { | ||
const unsubscribe = this.subscribe((shapeData) => { | ||
const unsubscribe = this.subscribe(({ value }) => { | ||
unsubscribe() | ||
if (this.#error) reject(this.#error) | ||
resolve(shapeData) | ||
resolve(value) | ||
}) | ||
@@ -89,3 +98,3 @@ } | ||
get valueSync() { | ||
get currentValue() { | ||
return this.#data | ||
@@ -199,5 +208,5 @@ } | ||
this.#subscribers.forEach((callback) => { | ||
callback(this.valueSync) | ||
callback({ value: this.currentValue, rows: this.currentRows }) | ||
}) | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
383324
4029
85