@rebasepro/server-postgres 0.10.0 → 0.10.1-canary.14e53ae
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/PostgresBootstrapper.d.ts +7 -3
- package/dist/auth/services.d.ts +43 -4
- package/dist/backup/backup-logic.d.ts +23 -0
- package/dist/backup/backup-service.d.ts +44 -2
- package/dist/backup/pg-tools.d.ts +41 -1
- package/dist/chunk-DSJWtz9O.js +40 -0
- package/dist/cli-helpers.d.ts +33 -1
- package/dist/ensure-collection-tables-CNlIONzj.js +304 -0
- package/dist/ensure-collection-tables-CNlIONzj.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +1472 -4640
- package/dist/index.es.js.map +1 -1
- package/dist/schema/auth-schema.d.ts +170 -0
- package/dist/schema/destructive-sql.d.ts +49 -0
- package/dist/schema/ensure-collection-tables.d.ts +79 -0
- package/dist/schema/generate-postgres-ddl-logic.d.ts +4 -1
- package/dist/services/cdc/CdcListener.d.ts +7 -14
- package/dist/services/channel-bus/ChannelBus.d.ts +29 -0
- package/dist/services/channel-bus/PostgresChannelBus.d.ts +111 -0
- package/dist/services/channel-bus/index.d.ts +55 -0
- package/dist/services/channel-history.d.ts +11 -0
- package/dist/services/channel-presence.d.ts +66 -0
- package/dist/services/pg-notify-listener.d.ts +47 -0
- package/dist/services/realtimeService.d.ts +114 -6
- package/dist/src-B0v4IKaI.js +329 -0
- package/dist/src-B0v4IKaI.js.map +1 -0
- package/dist/src-DmsRg8MR.js +4056 -0
- package/dist/src-DmsRg8MR.js.map +1 -0
- package/package.json +6 -6
- package/src/PostgresBootstrapper.ts +72 -3
- package/src/auth/ensure-tables.ts +91 -3
- package/src/auth/services.ts +186 -48
- package/src/backup/backup-cli.ts +60 -1
- package/src/backup/backup-cron.ts +24 -1
- package/src/backup/backup-logic.ts +62 -0
- package/src/backup/backup-service.ts +132 -13
- package/src/backup/pg-tools.ts +70 -2
- package/src/cli-helpers.ts +82 -27
- package/src/cli.ts +152 -6
- package/src/index.ts +4 -0
- package/src/schema/auth-schema.ts +41 -3
- package/src/schema/destructive-sql.ts +94 -0
- package/src/schema/ensure-collection-tables.test.ts +156 -0
- package/src/schema/ensure-collection-tables.ts +297 -0
- package/src/schema/generate-postgres-ddl-logic.ts +3 -3
- package/src/services/cdc/CdcListener.ts +27 -91
- package/src/services/channel-bus/ChannelBus.ts +44 -0
- package/src/services/channel-bus/PostgresChannelBus.ts +299 -0
- package/src/services/channel-bus/index.ts +123 -0
- package/src/services/channel-history.ts +35 -0
- package/src/services/channel-presence.ts +148 -0
- package/src/services/pg-notify-listener.ts +137 -0
- package/src/services/realtimeService.ts +383 -14
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A dedicated, self-healing Postgres `LISTEN` connection.
|
|
3
|
+
*
|
|
4
|
+
* Every cross-instance feature in the backend needs the same thing: one
|
|
5
|
+
* connection *outside* the Drizzle pool that stays open, holds a `LISTEN`, and
|
|
6
|
+
* comes back on its own after the database or the network drops it. CDC needed
|
|
7
|
+
* it first; the channel bus needs it too. This is that connection, with the one
|
|
8
|
+
* behaviour that matters to callers preserved: the **first** connect is
|
|
9
|
+
* validated and rethrown, so a caller can fall back to a different strategy,
|
|
10
|
+
* while every later drop is repaired quietly in the background.
|
|
11
|
+
*
|
|
12
|
+
* `LISTEN` is session state, so this connection must not go through a
|
|
13
|
+
* transaction-mode pooler (PgBouncer): give it the direct database URL.
|
|
14
|
+
*/
|
|
15
|
+
export interface PgNotifyListenerOptions {
|
|
16
|
+
/** Direct Postgres connection string (must bypass a transaction-mode pooler). */
|
|
17
|
+
connectionString: string;
|
|
18
|
+
/** NOTIFY channel to LISTEN on. Must be a plain identifier — it is interpolated. */
|
|
19
|
+
channel: string;
|
|
20
|
+
/** Called for every notification payload received. */
|
|
21
|
+
onPayload: (payload: string) => void | Promise<void>;
|
|
22
|
+
/** Prefix for log lines, e.g. `"[CDC]"`. */
|
|
23
|
+
logLabel: string;
|
|
24
|
+
/** Delay before a reconnect attempt. */
|
|
25
|
+
reconnectDelayMs?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare class PgNotifyListener {
|
|
28
|
+
private readonly options;
|
|
29
|
+
private client?;
|
|
30
|
+
private running;
|
|
31
|
+
private reconnectTimer?;
|
|
32
|
+
constructor(options: PgNotifyListenerOptions);
|
|
33
|
+
/** Whether the listener is meant to be connected right now. */
|
|
34
|
+
get active(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Connect and begin listening. Idempotent.
|
|
37
|
+
*
|
|
38
|
+
* Rejects if the *initial* connection or `LISTEN` fails, leaving the
|
|
39
|
+
* listener stopped — callers use that to degrade deliberately instead of
|
|
40
|
+
* running blind against a channel nothing is delivering.
|
|
41
|
+
*/
|
|
42
|
+
start(): Promise<void>;
|
|
43
|
+
/** Stop listening and release the connection. Idempotent. */
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
private connect;
|
|
46
|
+
private scheduleReconnect;
|
|
47
|
+
}
|
|
@@ -4,6 +4,7 @@ import { DataDriver, WebSocketMessage } from "@rebasepro/types";
|
|
|
4
4
|
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
5
5
|
import { RealtimeProvider, CollectionSubscriptionConfig, SingleSubscriptionConfig } from "../interfaces";
|
|
6
6
|
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
|
|
7
|
+
import { ChannelBus } from "./channel-bus";
|
|
7
8
|
import type { ChannelRetentionRule } from "@rebasepro/types";
|
|
8
9
|
/**
|
|
9
10
|
* Auth context stored per-subscription so real-time refetches respect RLS.
|
|
@@ -45,8 +46,33 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
45
46
|
* wait on each other.
|
|
46
47
|
*/
|
|
47
48
|
private channelSendQueues;
|
|
49
|
+
/**
|
|
50
|
+
* Cross-instance transport for channel frames and presence.
|
|
51
|
+
*
|
|
52
|
+
* Defaults to the memory bus, which publishes nowhere — so a single-instance
|
|
53
|
+
* deployment runs the same fan-out it always did, with one resolved promise
|
|
54
|
+
* per broadcast for company. See `channel-bus/ChannelBus.ts`.
|
|
55
|
+
*/
|
|
56
|
+
private bus;
|
|
57
|
+
/**
|
|
58
|
+
* The shared presence roster, present only when a real bus is active.
|
|
59
|
+
*
|
|
60
|
+
* Fan-out alone is not enough for presence: `presence_state` has to answer
|
|
61
|
+
* with everyone in the channel, and per-process maps can only answer for
|
|
62
|
+
* this replica's clients. See `channel-presence.ts`.
|
|
63
|
+
*/
|
|
64
|
+
private presenceStore?;
|
|
65
|
+
/** Sweeps roster rows left behind by instances that stopped heartbeating. */
|
|
66
|
+
private presenceSweepInterval?;
|
|
67
|
+
/**
|
|
68
|
+
* Channels whose oversized ephemeral broadcasts have already been reported,
|
|
69
|
+
* so a hot channel logs the problem once rather than once per message.
|
|
70
|
+
*/
|
|
71
|
+
private oversizedBroadcastWarned;
|
|
48
72
|
private presenceInterval?;
|
|
49
73
|
private static readonly PRESENCE_TIMEOUT_MS;
|
|
74
|
+
/** How often stale roster rows from other instances are reaped. */
|
|
75
|
+
private static readonly PRESENCE_SWEEP_INTERVAL_MS;
|
|
50
76
|
private dataService;
|
|
51
77
|
private _subscriptions;
|
|
52
78
|
private subscriptionCallbacks;
|
|
@@ -245,6 +271,44 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
245
271
|
private persistAndFanOut;
|
|
246
272
|
/** Deliver a broadcast frame to every member of a channel but the sender. */
|
|
247
273
|
private fanOutBroadcast;
|
|
274
|
+
/**
|
|
275
|
+
* Install the transport that carries channel frames between instances.
|
|
276
|
+
*
|
|
277
|
+
* Called once at boot. A bus that cannot start is reported and replaced with
|
|
278
|
+
* the memory bus: losing cross-instance fan-out degrades collaboration to
|
|
279
|
+
* what it was before this existed, whereas refusing to boot takes the whole
|
|
280
|
+
* backend down for it.
|
|
281
|
+
*/
|
|
282
|
+
configureChannelBus(bus: ChannelBus): Promise<void>;
|
|
283
|
+
/** Which transport is in use — `"memory"` means per-instance only. */
|
|
284
|
+
getChannelBusKind(): ChannelBus["kind"];
|
|
285
|
+
/**
|
|
286
|
+
* Send a broadcast to the other instances.
|
|
287
|
+
*
|
|
288
|
+
* Fire-and-forget by design: the clients on this instance have already been
|
|
289
|
+
* served, and a bus that is briefly unreachable must not turn a broadcast
|
|
290
|
+
* into an error for the sender.
|
|
291
|
+
*/
|
|
292
|
+
private publishBroadcast;
|
|
293
|
+
private publishFrame;
|
|
294
|
+
/**
|
|
295
|
+
* Tell the sender that a message was delivered locally but nowhere else.
|
|
296
|
+
*
|
|
297
|
+
* Staying quiet here would be the worst option available: on one instance
|
|
298
|
+
* the app works, on two it works for half the users, and nothing in the
|
|
299
|
+
* logs connects the two. The fix is a one-liner in config — give the
|
|
300
|
+
* channel a retention rule and the message travels as a pointer instead —
|
|
301
|
+
* so the message says exactly that.
|
|
302
|
+
*/
|
|
303
|
+
private reportOversizedBroadcast;
|
|
304
|
+
/**
|
|
305
|
+
* Deliver a frame published by another instance to this one's clients.
|
|
306
|
+
*
|
|
307
|
+
* Frames we published ourselves are dropped on arrival — the local fan-out
|
|
308
|
+
* happened before the publish — exactly as the entity-change handler skips
|
|
309
|
+
* its own `sid`.
|
|
310
|
+
*/
|
|
311
|
+
private handleBusFrame;
|
|
248
312
|
/**
|
|
249
313
|
* Install retention rules and create the tables they need.
|
|
250
314
|
*
|
|
@@ -266,16 +330,60 @@ export declare class RealtimeService extends EventEmitter implements RealtimePro
|
|
|
266
330
|
*/
|
|
267
331
|
private handleChannelHistoryRequest;
|
|
268
332
|
private sendChannelHistory;
|
|
269
|
-
/**
|
|
333
|
+
/**
|
|
334
|
+
* Track presence in a channel.
|
|
335
|
+
*
|
|
336
|
+
* The client re-sends this every ~20s as a heartbeat against the 30s
|
|
337
|
+
* timeout, so most calls carry the state that is already recorded. Those
|
|
338
|
+
* refresh `last_seen` and stop there: re-announcing an unchanged state to
|
|
339
|
+
* every instance would put a bus message per client per heartbeat on the
|
|
340
|
+
* wire to tell everyone nothing happened.
|
|
341
|
+
*/
|
|
270
342
|
trackPresence(clientId: string, channel: string, state: Record<string, unknown>): void;
|
|
271
|
-
/**
|
|
272
|
-
|
|
273
|
-
|
|
343
|
+
/**
|
|
344
|
+
* Remove presence from a channel.
|
|
345
|
+
*
|
|
346
|
+
* `skipStore` is for the socket-close path, which clears every channel at
|
|
347
|
+
* once and then deletes the client's rows in a single statement instead of
|
|
348
|
+
* one per channel.
|
|
349
|
+
*/
|
|
350
|
+
removePresence(clientId: string, channel: string, options?: {
|
|
351
|
+
skipStore?: boolean;
|
|
352
|
+
}): void;
|
|
353
|
+
/**
|
|
354
|
+
* Send the full roster for a channel to one client.
|
|
355
|
+
*
|
|
356
|
+
* Answered from the shared table when there is one, because "who is in this
|
|
357
|
+
* document?" has a single answer that must not depend on which replica the
|
|
358
|
+
* asker happens to be connected to. Without a bus there is nothing to share
|
|
359
|
+
* and the local map *is* the roster — that path stays synchronous, which is
|
|
360
|
+
* what it always was.
|
|
361
|
+
*/
|
|
274
362
|
sendPresenceState(clientId: string, channel: string): void;
|
|
275
|
-
/**
|
|
276
|
-
private
|
|
363
|
+
/** Presence of the clients connected to this instance. */
|
|
364
|
+
private localPresences;
|
|
365
|
+
private sendPresenceStateMessage;
|
|
366
|
+
/** Deliver a presence diff to this instance's members of the channel. */
|
|
367
|
+
private deliverPresenceDiff;
|
|
368
|
+
/** Tell the other instances about a presence change. */
|
|
369
|
+
private publishPresenceDiff;
|
|
370
|
+
/** Run a roster write when there is a roster, and never let it throw. */
|
|
371
|
+
private presenceStoreOp;
|
|
277
372
|
/** Periodic cleanup for stale presences */
|
|
278
373
|
private ensurePresenceCleanup;
|
|
374
|
+
/**
|
|
375
|
+
* Reap roster rows whose owning instance stopped heartbeating.
|
|
376
|
+
*
|
|
377
|
+
* This is the cross-instance half of the sweep above, and it doubles as
|
|
378
|
+
* crash recovery: a pod that dies takes its clients with it but leaves
|
|
379
|
+
* their rows behind, and after one TTL window they look exactly like any
|
|
380
|
+
* other client that went quiet. The delete returns what it removed, so
|
|
381
|
+
* whichever instance wins the race is the one that announces the
|
|
382
|
+
* departures — once for the cluster, not once per replica.
|
|
383
|
+
*/
|
|
384
|
+
private ensurePresenceSweep;
|
|
385
|
+
/** One pass of the stale-roster sweep. See {@link ensurePresenceSweep}. */
|
|
386
|
+
private sweepStalePresence;
|
|
279
387
|
/**
|
|
280
388
|
* Gracefully tear down all realtime resources.
|
|
281
389
|
*
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "module";
|
|
2
|
+
import "process";
|
|
3
|
+
__createRequire(import.meta.url);
|
|
4
|
+
//#region ../types/src/types/entities.ts
|
|
5
|
+
/**
|
|
6
|
+
* Class used to create a reference to a entity in a different path
|
|
7
|
+
*/
|
|
8
|
+
var EntityRelation = class {
|
|
9
|
+
__type = "relation";
|
|
10
|
+
/**
|
|
11
|
+
* ID of the entity
|
|
12
|
+
*/
|
|
13
|
+
id;
|
|
14
|
+
/**
|
|
15
|
+
* A string representing the path of the referenced document (relative
|
|
16
|
+
* to the root of the database).
|
|
17
|
+
*/
|
|
18
|
+
path;
|
|
19
|
+
/**
|
|
20
|
+
* Pre-fetched data payload to eliminate N+1 queries.
|
|
21
|
+
* When present, clients can use this directly instead of fetching.
|
|
22
|
+
*/
|
|
23
|
+
data;
|
|
24
|
+
constructor(id, path, data) {
|
|
25
|
+
this.id = id;
|
|
26
|
+
this.path = path;
|
|
27
|
+
this.data = data;
|
|
28
|
+
}
|
|
29
|
+
get pathWithId() {
|
|
30
|
+
return `${this.path}/${this.id}`;
|
|
31
|
+
}
|
|
32
|
+
isEntityReference() {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
isEntityRelation() {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var Vector = class {
|
|
40
|
+
value;
|
|
41
|
+
constructor(value) {
|
|
42
|
+
this.value = value;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region ../types/src/types/filter-operators.ts
|
|
47
|
+
/** Maps REST short-code operators to their canonical equivalents. */
|
|
48
|
+
var REST_TO_CANONICAL = {
|
|
49
|
+
"eq": "==",
|
|
50
|
+
"neq": "!=",
|
|
51
|
+
"gt": ">",
|
|
52
|
+
"gte": ">=",
|
|
53
|
+
"lt": "<",
|
|
54
|
+
"lte": "<=",
|
|
55
|
+
"in": "in",
|
|
56
|
+
"nin": "not-in",
|
|
57
|
+
"cs": "array-contains",
|
|
58
|
+
"csa": "array-contains-any",
|
|
59
|
+
"like": "like",
|
|
60
|
+
"ilike": "ilike",
|
|
61
|
+
"nlike": "not-like",
|
|
62
|
+
"nilike": "not-ilike",
|
|
63
|
+
"isnull": "is-null",
|
|
64
|
+
"notnull": "is-not-null"
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Operators that test for null/not-null and therefore ignore their value.
|
|
68
|
+
* Codecs normalize the value of these conditions to `null`.
|
|
69
|
+
*/
|
|
70
|
+
var NULL_OPS = new Set(["is-null", "is-not-null"]);
|
|
71
|
+
/**
|
|
72
|
+
* Every canonical operator, in a stable order. Useful for engine capability
|
|
73
|
+
* declarations ({@link DataSourceCapabilities.filterOperators}) and for
|
|
74
|
+
* building operator subsets.
|
|
75
|
+
* @group Models
|
|
76
|
+
*/
|
|
77
|
+
var ALL_WHERE_FILTER_OPS = [
|
|
78
|
+
"<",
|
|
79
|
+
"<=",
|
|
80
|
+
"==",
|
|
81
|
+
"!=",
|
|
82
|
+
">=",
|
|
83
|
+
">",
|
|
84
|
+
"in",
|
|
85
|
+
"not-in",
|
|
86
|
+
"array-contains",
|
|
87
|
+
"array-contains-any",
|
|
88
|
+
"like",
|
|
89
|
+
"ilike",
|
|
90
|
+
"not-like",
|
|
91
|
+
"not-ilike",
|
|
92
|
+
"is-null",
|
|
93
|
+
"is-not-null"
|
|
94
|
+
];
|
|
95
|
+
/** All canonical operator strings for runtime validation. */
|
|
96
|
+
var CANONICAL_OPS = new Set(ALL_WHERE_FILTER_OPS);
|
|
97
|
+
/**
|
|
98
|
+
* Resolve any operator string (canonical or REST short-code) to its
|
|
99
|
+
* canonical `WhereFilterOp` form. Returns `undefined` for unknown operators.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* toCanonicalOp("==") // "=="
|
|
103
|
+
* toCanonicalOp("eq") // "=="
|
|
104
|
+
* toCanonicalOp("cs") // "array-contains"
|
|
105
|
+
* toCanonicalOp("xyz") // undefined
|
|
106
|
+
*/
|
|
107
|
+
function toCanonicalOp(op) {
|
|
108
|
+
if (CANONICAL_OPS.has(op)) return op;
|
|
109
|
+
return REST_TO_CANONICAL[op];
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region ../types/src/types/collections.ts
|
|
113
|
+
/**
|
|
114
|
+
* Type guard for PostgreSQL collections.
|
|
115
|
+
* Returns true if the collection uses the Postgres engine (or the default engine).
|
|
116
|
+
* @group Models
|
|
117
|
+
*/
|
|
118
|
+
function isPostgresCollectionConfig(collection) {
|
|
119
|
+
return !collection.engine || collection.engine === "postgres";
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Reads a collection's driver-declared subcollections thunk (the `subcollections`
|
|
123
|
+
* field) independent of engine identity, so engine-agnostic code doesn't have to
|
|
124
|
+
* type-guard against a specific driver. Returns `undefined` when the collection
|
|
125
|
+
* declares none.
|
|
126
|
+
*
|
|
127
|
+
* Pair with `getDataSourceCapabilities(engine).supportsSubcollections` to decide
|
|
128
|
+
* whether the engine honours subcollections at all before reading them.
|
|
129
|
+
* @group Models
|
|
130
|
+
*/
|
|
131
|
+
function getDeclaredSubcollections(collection) {
|
|
132
|
+
return collection.subcollections;
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region ../types/src/types/policy.ts
|
|
136
|
+
/**
|
|
137
|
+
* The id a request without a logged-in user reports as `auth.uid()`.
|
|
138
|
+
*
|
|
139
|
+
* A user-context request always sets `app.uid`: blank would read back as
|
|
140
|
+
* `NULL`, and `NULL` is how the trusted server context is recognised, so an
|
|
141
|
+
* anonymous visitor would be promoted to server privileges. The driver
|
|
142
|
+
* therefore substitutes this sentinel at the single chokepoint where the GUC
|
|
143
|
+
* is set.
|
|
144
|
+
*
|
|
145
|
+
* The consequence for policy authors is that **`auth.uid() IS NOT NULL` is a
|
|
146
|
+
* tautology on the user path** — it is true for anonymous visitors too. Use
|
|
147
|
+
* {@link policy.authenticated} (or `auth.uid() <> 'anonymous'`) to mean "signed
|
|
148
|
+
* in", and {@link policy.serverContext} to mean "the trusted server context".
|
|
149
|
+
*
|
|
150
|
+
* @group Models
|
|
151
|
+
*/
|
|
152
|
+
var ANONYMOUS_USER_ID = "anonymous";
|
|
153
|
+
/** @group Models */
|
|
154
|
+
var policy = {
|
|
155
|
+
true: () => ({ kind: "true" }),
|
|
156
|
+
false: () => ({ kind: "false" }),
|
|
157
|
+
and: (...operands) => ({
|
|
158
|
+
kind: "and",
|
|
159
|
+
operands
|
|
160
|
+
}),
|
|
161
|
+
or: (...operands) => ({
|
|
162
|
+
kind: "or",
|
|
163
|
+
operands
|
|
164
|
+
}),
|
|
165
|
+
not: (operand) => ({
|
|
166
|
+
kind: "not",
|
|
167
|
+
operand
|
|
168
|
+
}),
|
|
169
|
+
compare: (left, op, right) => ({
|
|
170
|
+
kind: "compare",
|
|
171
|
+
op,
|
|
172
|
+
left,
|
|
173
|
+
right
|
|
174
|
+
}),
|
|
175
|
+
rolesOverlap: (roles) => ({
|
|
176
|
+
kind: "rolesOverlap",
|
|
177
|
+
roles
|
|
178
|
+
}),
|
|
179
|
+
rolesContain: (roles) => ({
|
|
180
|
+
kind: "rolesContain",
|
|
181
|
+
roles
|
|
182
|
+
}),
|
|
183
|
+
authenticated: () => ({ kind: "authenticated" }),
|
|
184
|
+
serverContext: () => ({ kind: "serverContext" }),
|
|
185
|
+
existsIn: (args) => ({
|
|
186
|
+
kind: "existsIn",
|
|
187
|
+
collection: args.collection,
|
|
188
|
+
where: args.where
|
|
189
|
+
}),
|
|
190
|
+
raw: (sql) => ({
|
|
191
|
+
kind: "raw",
|
|
192
|
+
sql
|
|
193
|
+
}),
|
|
194
|
+
field: (name) => ({
|
|
195
|
+
kind: "field",
|
|
196
|
+
name
|
|
197
|
+
}),
|
|
198
|
+
outerField: (name) => ({
|
|
199
|
+
kind: "outerField",
|
|
200
|
+
name
|
|
201
|
+
}),
|
|
202
|
+
literal: (value) => ({
|
|
203
|
+
kind: "literal",
|
|
204
|
+
value
|
|
205
|
+
}),
|
|
206
|
+
authUid: () => ({ kind: "authUid" }),
|
|
207
|
+
authRoles: () => ({ kind: "authRoles" })
|
|
208
|
+
};
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region ../types/src/types/backend.ts
|
|
211
|
+
/**
|
|
212
|
+
* Type guard: does this admin support SQL operations?
|
|
213
|
+
* @group Admin
|
|
214
|
+
*/
|
|
215
|
+
function isSQLAdmin(admin) {
|
|
216
|
+
return !!admin && typeof admin.executeSql === "function";
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Type guard: does this admin support schema management?
|
|
220
|
+
* @group Admin
|
|
221
|
+
*/
|
|
222
|
+
function isSchemaAdmin(admin) {
|
|
223
|
+
return !!admin && (typeof admin.fetchUnmappedTables === "function" || typeof admin.fetchTableMetadata === "function");
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region ../types/src/types/channel_bus.ts
|
|
227
|
+
/**
|
|
228
|
+
* Whether `setting` is an already-constructed transport rather than a request
|
|
229
|
+
* for a built-in one.
|
|
230
|
+
*
|
|
231
|
+
* Structural rather than nominal so that an instance from a *different copy* of
|
|
232
|
+
* `@rebasepro/types` — an entirely normal outcome of a separately versioned
|
|
233
|
+
* transport package — is still recognised.
|
|
234
|
+
*/
|
|
235
|
+
function isChannelBusInstance(setting) {
|
|
236
|
+
return typeof setting?.publish === "function";
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region ../types/src/types/data_source.ts
|
|
240
|
+
/**
|
|
241
|
+
* The default data-source key, used when a collection does not name a
|
|
242
|
+
* `dataSource`. Shared by the frontend router and the backend driver
|
|
243
|
+
* registry so both agree on "the default database".
|
|
244
|
+
* @group Models
|
|
245
|
+
*/
|
|
246
|
+
var DEFAULT_DATA_SOURCE_KEY = "(default)";
|
|
247
|
+
/** @group Models */
|
|
248
|
+
var POSTGRES_CAPABILITIES = {
|
|
249
|
+
key: "postgres",
|
|
250
|
+
label: "PostgreSQL",
|
|
251
|
+
supportsRelations: true,
|
|
252
|
+
supportsSubcollections: false,
|
|
253
|
+
supportsRLS: true,
|
|
254
|
+
supportsReferences: false,
|
|
255
|
+
supportsColumnTypes: true,
|
|
256
|
+
supportsRealtime: true,
|
|
257
|
+
filterOperators: ALL_WHERE_FILTER_OPS,
|
|
258
|
+
supportsSQLAdmin: true,
|
|
259
|
+
supportsDocumentAdmin: false,
|
|
260
|
+
supportsSchemaAdmin: true
|
|
261
|
+
};
|
|
262
|
+
/** @group Models */
|
|
263
|
+
var FIREBASE_CAPABILITIES = {
|
|
264
|
+
key: "firestore",
|
|
265
|
+
label: "Firebase / Firestore",
|
|
266
|
+
supportsRelations: false,
|
|
267
|
+
supportsSubcollections: true,
|
|
268
|
+
supportsRLS: false,
|
|
269
|
+
supportsReferences: true,
|
|
270
|
+
supportsColumnTypes: false,
|
|
271
|
+
supportsRealtime: true,
|
|
272
|
+
filterOperators: ALL_WHERE_FILTER_OPS.filter((op) => op !== "like" && op !== "ilike" && op !== "not-like" && op !== "not-ilike"),
|
|
273
|
+
supportsSQLAdmin: false,
|
|
274
|
+
supportsDocumentAdmin: false,
|
|
275
|
+
supportsSchemaAdmin: false
|
|
276
|
+
};
|
|
277
|
+
/** @group Models */
|
|
278
|
+
var MONGODB_CAPABILITIES = {
|
|
279
|
+
key: "mongodb",
|
|
280
|
+
label: "MongoDB",
|
|
281
|
+
supportsRelations: false,
|
|
282
|
+
supportsSubcollections: true,
|
|
283
|
+
supportsRLS: false,
|
|
284
|
+
supportsReferences: true,
|
|
285
|
+
supportsColumnTypes: false,
|
|
286
|
+
supportsRealtime: false,
|
|
287
|
+
filterOperators: ALL_WHERE_FILTER_OPS,
|
|
288
|
+
supportsSQLAdmin: false,
|
|
289
|
+
supportsDocumentAdmin: true,
|
|
290
|
+
supportsSchemaAdmin: true
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* Fallback capabilities when the driver is unknown.
|
|
294
|
+
* Enables everything so nothing is hidden unexpectedly.
|
|
295
|
+
* @group Models
|
|
296
|
+
*/
|
|
297
|
+
var DEFAULT_CAPABILITIES = {
|
|
298
|
+
key: "(default)",
|
|
299
|
+
label: "Default",
|
|
300
|
+
supportsRelations: true,
|
|
301
|
+
supportsSubcollections: true,
|
|
302
|
+
supportsRLS: true,
|
|
303
|
+
supportsReferences: true,
|
|
304
|
+
supportsColumnTypes: true,
|
|
305
|
+
supportsRealtime: true,
|
|
306
|
+
filterOperators: ALL_WHERE_FILTER_OPS,
|
|
307
|
+
supportsSQLAdmin: true,
|
|
308
|
+
supportsDocumentAdmin: true,
|
|
309
|
+
supportsSchemaAdmin: true
|
|
310
|
+
};
|
|
311
|
+
var CAPABILITIES_REGISTRY = {
|
|
312
|
+
postgres: POSTGRES_CAPABILITIES,
|
|
313
|
+
firestore: FIREBASE_CAPABILITIES,
|
|
314
|
+
mongodb: MONGODB_CAPABILITIES,
|
|
315
|
+
"(default)": DEFAULT_CAPABILITIES
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* Look up capabilities for a given engine key.
|
|
319
|
+
* If `engine` is undefined or not found, returns `DEFAULT_CAPABILITIES`.
|
|
320
|
+
* @group Models
|
|
321
|
+
*/
|
|
322
|
+
function getDataSourceCapabilities(engine) {
|
|
323
|
+
if (!engine) return POSTGRES_CAPABILITIES;
|
|
324
|
+
return CAPABILITIES_REGISTRY[engine] ?? DEFAULT_CAPABILITIES;
|
|
325
|
+
}
|
|
326
|
+
//#endregion
|
|
327
|
+
export { isSchemaAdmin as a, getDeclaredSubcollections as c, REST_TO_CANONICAL as d, toCanonicalOp as f, isSQLAdmin as i, isPostgresCollectionConfig as l, Vector as m, getDataSourceCapabilities as n, ANONYMOUS_USER_ID as o, EntityRelation as p, isChannelBusInstance as r, policy as s, DEFAULT_DATA_SOURCE_KEY as t, NULL_OPS as u };
|
|
328
|
+
|
|
329
|
+
//# sourceMappingURL=src-B0v4IKaI.js.map
|