-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathtypes.ts
353 lines (293 loc) · 9.65 KB
/
types.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
import { JSONBig, JsonContainer } from '@powersync/service-jsonbig';
import { ColumnDefinition } from './ExpressionType.js';
import { SourceTableInterface } from './SourceTableInterface.js';
import { SyncRulesOptions } from './SqlSyncRules.js';
import { TablePattern } from './TablePattern.js';
import { toSyncRulesParameters } from './utils.js';
import { BucketDescription, BucketPriority } from './BucketDescription.js';
import { ParameterLookup } from './BucketParameterQuerier.js';
export interface SyncRules {
evaluateRow(options: EvaluateRowOptions): EvaluationResult[];
evaluateParameterRow(table: SourceTableInterface, row: SqliteRow): EvaluatedParametersResult[];
}
export interface QueryParseOptions extends SyncRulesOptions {
accept_potentially_dangerous_queries?: boolean;
priority?: BucketPriority;
}
export interface EvaluatedParameters {
lookup: ParameterLookup;
/**
* Parameters used to generate bucket id. May be incomplete.
*
* JSON-serializable.
*/
bucket_parameters: Record<string, SqliteJsonValue>[];
}
export type EvaluatedParametersResult = EvaluatedParameters | EvaluationError;
export interface EvaluatedRow {
bucket: string;
/** Output table - may be different from input table. */
table: string;
/**
* Convenience attribute. Must match data.id.
*/
id: string;
/** Must be JSON-serializable. */
data: SqliteJsonRow;
/** For debugging purposes only. */
ruleId?: string;
}
export interface EvaluationError {
error: string;
}
export function isEvaluationError(e: any): e is EvaluationError {
return typeof e.error == 'string';
}
export function isEvaluatedRow(e: EvaluationResult): e is EvaluatedRow {
return typeof (e as EvaluatedRow).bucket == 'string';
}
export function isEvaluatedParameters(e: EvaluatedParametersResult): e is EvaluatedParameters {
return Array.isArray((e as EvaluatedParameters).lookup?.values);
}
export type EvaluationResult = EvaluatedRow | EvaluationError;
export interface RequestJwtPayload {
/**
* user_id
*/
sub: string;
[key: string]: any;
}
export interface ParameterValueSet {
lookup(table: string, column: string): SqliteValue;
/**
* JSON string of raw request parameters.
*/
raw_user_parameters: string;
/**
* JSON string of raw request parameters.
*/
raw_token_payload: string;
user_id: string;
}
export class RequestParameters implements ParameterValueSet {
token_parameters: SqliteJsonRow;
user_parameters: SqliteJsonRow;
/**
* JSON string of raw request parameters.
*/
raw_user_parameters: string;
/**
* JSON string of raw request parameters.
*/
raw_token_payload: string;
user_id: string;
constructor(tokenPayload: RequestJwtPayload, clientParameters: Record<string, any>) {
// This type is verified when we verify the token
const legacyParameters = tokenPayload.parameters as Record<string, any> | undefined;
const token_parameters = {
...legacyParameters,
// sub takes presedence over any embedded parameters
user_id: tokenPayload.sub
};
this.token_parameters = toSyncRulesParameters(token_parameters);
this.user_id = tokenPayload.sub;
this.raw_token_payload = JSONBig.stringify(tokenPayload);
this.raw_user_parameters = JSONBig.stringify(clientParameters);
this.user_parameters = toSyncRulesParameters(clientParameters);
}
lookup(table: string, column: string): SqliteJsonValue {
if (table == 'token_parameters') {
return this.token_parameters[column];
} else if (table == 'user_parameters') {
return this.user_parameters[column];
}
throw new Error(`Unknown table: ${table}`);
}
}
/**
* A value that is both SQLite and JSON-compatible.
*
* Uint8Array is not supported.
*/
export type SqliteJsonValue = number | string | bigint | null;
/**
* A value supported by the SQLite type system.
*/
export type SqliteValue = number | string | null | bigint | Uint8Array;
/**
* A set of values that are both SQLite and JSON-compatible.
*
* This is a flat object -> no nested arrays or objects.
*/
export type SqliteJsonRow = { [column: string]: SqliteJsonValue };
/**
* SQLite-compatible row (NULL, TEXT, INTEGER, REAL, BLOB).
* JSON is represented as TEXT.
*/
export type SqliteRow = { [column: string]: SqliteValue };
/**
* SQLite-compatible row (NULL, TEXT, INTEGER, REAL, BLOB).
* JSON is represented as TEXT.
*
* Toasted values are `undefined`.
*/
export type ToastableSqliteRow = { [column: string]: SqliteValue | undefined };
/**
* A value as received from the database.
*/
export type DatabaseInputValue =
| SqliteValue
| boolean
| DatabaseInputValue[]
| JsonContainer
| { [key: string]: DatabaseInputValue };
/**
* Database input row. Can contain nested arrays and objects.
*/
export type DatabaseInputRow = { [column: string]: DatabaseInputValue };
/**
* A set of known parameters that a query is evaluated on.
*/
export type QueryParameters = { [table: string]: SqliteRow };
/**
* A single set of parameters that would make a WHERE filter true.
*
* Each parameter is prefixed with a table name, e.g. 'bucket.param'.
*
* Data queries: this is converted into a bucket id, given named bucket parameters.
*
* Parameter queries: this is converted into a lookup array.
*/
export type FilterParameters = { [parameter: string]: SqliteJsonValue };
export interface InputParameter {
/**
* An unique identifier per parameter in a query.
*
* This is used to identify the same parameters used in a query multiple times.
*
* The value itself does not necessarily have any specific meaning.
*/
key: string;
/**
* True if the parameter expands to an array. This means parametersToLookupValue() can
* return a JSON array. This is different from `unbounded` on the clause.
*/
expands: boolean;
/**
* Given FilterParameters from a data row, return the associated value.
*
* Only relevant for parameter queries.
*/
filteredRowToLookupValue(filterParameters: FilterParameters): SqliteJsonValue;
/**
* Given RequestParameters, return the associated value to lookup.
*
* Only relevant for parameter queries.
*/
parametersToLookupValue(parameters: ParameterValueSet): SqliteValue;
}
export interface EvaluateRowOptions {
sourceTable: SourceTableInterface;
record: SqliteRow;
}
/**
* This is a clause that matches row and parameter values.
*
* Example:
* [WHERE] users.org_id = bucket.org_id
*
* For a given a row, this produces a set of parameters that would make the clause evaluate to true.
*/
export interface ParameterMatchClause {
error: boolean;
/**
* The parameter fields that are used for this filter, for example:
* * ['bucket.region_id'] for a data query
* * ['token_parameters.user_id'] for a parameter query
*
* These parameters are always matched by this clause, and no additional parameters are matched.
*/
inputParameters: InputParameter[];
/**
* True if the filter depends on an unbounded array column. This means filterRow can return
* multiple items.
*
* We restrict filters to only allow a single unbounded column for bucket parameters, otherwise the number of
* bucketParameter combinations could grow too much.
*/
unbounded: boolean;
/**
* Given a data row, give a set of filter parameters that would make the filter be true.
*
* For StaticSqlParameterQuery, the tables are token_parameters and user_parameters.
* For others, it is the table of the data or parameter query.
*
* @param tables - {table => row}
* @return The filter parameters
*/
filterRow(tables: QueryParameters): TrueIfParametersMatch;
/** request.user_id(), request.jwt(), token_parameters.* */
usesAuthenticatedRequestParameters: boolean;
/** request.parameters(), user_parameters.* */
usesUnauthenticatedRequestParameters: boolean;
}
/**
* This is a clause that operates on request or bucket parameters.
*/
export interface ParameterValueClause {
/** request.user_id(), request.jwt(), token_parameters.* */
usesAuthenticatedRequestParameters: boolean;
/** request.parameters(), user_parameters.* */
usesUnauthenticatedRequestParameters: boolean;
/**
* An unique key for the clause.
*
* For bucket parameters, this is `bucket.${name}`.
* For expressions, the exact format is undefined.
*/
key: string;
/**
* Given RequestParameters, return the associated value to lookup.
*
* Only relevant for parameter queries.
*/
lookupParameterValue(parameters: ParameterValueSet): SqliteValue;
}
export interface QuerySchema {
getColumn(table: string, column: string): ColumnDefinition | undefined;
getColumns(table: string): ColumnDefinition[];
}
/**
* A clause that uses row values as input.
*
* For parameter queries, that is the parameter table being queried.
* For data queries, that is the data table being queried.
*/
export interface RowValueClause {
evaluate(tables: QueryParameters): SqliteValue;
getColumnDefinition(schema: QuerySchema): ColumnDefinition | undefined;
}
/**
* Completely static value.
*
* Extends RowValueClause and ParameterValueClause to simplify code in some places.
*/
export interface StaticValueClause extends RowValueClause, ParameterValueClause {
readonly value: SqliteValue;
}
export interface ClauseError {
error: true;
}
export type CompiledClause = RowValueClause | ParameterMatchClause | ParameterValueClause | ClauseError;
/**
* true if any of the filter parameter sets match
*/
export type TrueIfParametersMatch = FilterParameters[];
export interface SourceSchemaTable {
table: string;
getColumn(column: string): ColumnDefinition | undefined;
getColumns(): ColumnDefinition[];
}
export interface SourceSchema {
getTables(sourceTable: TablePattern): SourceSchemaTable[];
}