@powersync/service-sync-rules 0.28.0 → 0.29.1

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.
Files changed (97) hide show
  1. package/LICENSE +3 -3
  2. package/dist/BaseSqlDataQuery.d.ts +9 -1
  3. package/dist/BaseSqlDataQuery.js +42 -0
  4. package/dist/BaseSqlDataQuery.js.map +1 -1
  5. package/dist/BucketDescription.d.ts +16 -0
  6. package/dist/BucketParameterQuerier.d.ts +19 -3
  7. package/dist/BucketParameterQuerier.js.map +1 -1
  8. package/dist/BucketSource.d.ts +72 -0
  9. package/dist/BucketSource.js +6 -0
  10. package/dist/BucketSource.js.map +1 -0
  11. package/dist/ExpressionType.d.ts +2 -1
  12. package/dist/ExpressionType.js +1 -1
  13. package/dist/ExpressionType.js.map +1 -1
  14. package/dist/SqlBucketDescriptor.d.ts +40 -8
  15. package/dist/SqlBucketDescriptor.js +78 -10
  16. package/dist/SqlBucketDescriptor.js.map +1 -1
  17. package/dist/SqlDataQuery.d.ts +4 -3
  18. package/dist/SqlDataQuery.js +10 -30
  19. package/dist/SqlDataQuery.js.map +1 -1
  20. package/dist/SqlParameterQuery.d.ts +4 -4
  21. package/dist/SqlParameterQuery.js +9 -4
  22. package/dist/SqlParameterQuery.js.map +1 -1
  23. package/dist/SqlSyncRules.d.ts +55 -7
  24. package/dist/SqlSyncRules.js +126 -47
  25. package/dist/SqlSyncRules.js.map +1 -1
  26. package/dist/StaticSqlParameterQuery.d.ts +2 -2
  27. package/dist/StaticSqlParameterQuery.js +3 -2
  28. package/dist/StaticSqlParameterQuery.js.map +1 -1
  29. package/dist/TableValuedFunctionSqlParameterQuery.d.ts +2 -2
  30. package/dist/TableValuedFunctionSqlParameterQuery.js +11 -10
  31. package/dist/TableValuedFunctionSqlParameterQuery.js.map +1 -1
  32. package/dist/TableValuedFunctions.d.ts +2 -2
  33. package/dist/TableValuedFunctions.js +38 -35
  34. package/dist/TableValuedFunctions.js.map +1 -1
  35. package/dist/compatibility.d.ts +40 -0
  36. package/dist/compatibility.js +56 -0
  37. package/dist/compatibility.js.map +1 -0
  38. package/dist/events/SqlEventDescriptor.d.ts +3 -1
  39. package/dist/events/SqlEventDescriptor.js +4 -2
  40. package/dist/events/SqlEventDescriptor.js.map +1 -1
  41. package/dist/events/SqlEventSourceQuery.d.ts +2 -1
  42. package/dist/events/SqlEventSourceQuery.js +3 -2
  43. package/dist/events/SqlEventSourceQuery.js.map +1 -1
  44. package/dist/index.d.ts +7 -0
  45. package/dist/index.js +7 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/json_schema.js +54 -1
  48. package/dist/json_schema.js.map +1 -1
  49. package/dist/request_functions.d.ts +24 -4
  50. package/dist/request_functions.js +68 -17
  51. package/dist/request_functions.js.map +1 -1
  52. package/dist/schema-generators/SchemaGenerator.js +2 -12
  53. package/dist/schema-generators/SchemaGenerator.js.map +1 -1
  54. package/dist/sql_filters.d.ts +25 -2
  55. package/dist/sql_filters.js +223 -119
  56. package/dist/sql_filters.js.map +1 -1
  57. package/dist/sql_functions.d.ts +13 -31
  58. package/dist/sql_functions.js +191 -114
  59. package/dist/sql_functions.js.map +1 -1
  60. package/dist/sql_support.d.ts +20 -0
  61. package/dist/sql_support.js +67 -14
  62. package/dist/sql_support.js.map +1 -1
  63. package/dist/streams/filter.d.ts +148 -0
  64. package/dist/streams/filter.js +426 -0
  65. package/dist/streams/filter.js.map +1 -0
  66. package/dist/streams/from_sql.d.ts +4 -0
  67. package/dist/streams/from_sql.js +375 -0
  68. package/dist/streams/from_sql.js.map +1 -0
  69. package/dist/streams/functions.d.ts +2 -0
  70. package/dist/streams/functions.js +38 -0
  71. package/dist/streams/functions.js.map +1 -0
  72. package/dist/streams/parameter.d.ts +67 -0
  73. package/dist/streams/parameter.js +2 -0
  74. package/dist/streams/parameter.js.map +1 -0
  75. package/dist/streams/stream.d.ts +40 -0
  76. package/dist/streams/stream.js +139 -0
  77. package/dist/streams/stream.js.map +1 -0
  78. package/dist/streams/utils.d.ts +1 -0
  79. package/dist/streams/utils.js +13 -0
  80. package/dist/streams/utils.js.map +1 -0
  81. package/dist/streams/variant.d.ts +122 -0
  82. package/dist/streams/variant.js +266 -0
  83. package/dist/streams/variant.js.map +1 -0
  84. package/dist/types/custom_sqlite_value.d.ts +38 -0
  85. package/dist/types/custom_sqlite_value.js +50 -0
  86. package/dist/types/custom_sqlite_value.js.map +1 -0
  87. package/dist/types/time.d.ts +33 -0
  88. package/dist/types/time.js +67 -0
  89. package/dist/types/time.js.map +1 -0
  90. package/dist/types.d.ts +52 -9
  91. package/dist/types.js +28 -2
  92. package/dist/types.js.map +1 -1
  93. package/dist/utils.d.ts +9 -6
  94. package/dist/utils.js +42 -17
  95. package/dist/utils.js.map +1 -1
  96. package/package.json +3 -3
  97. package/schema/sync_rules.json +84 -2
@@ -0,0 +1,139 @@
1
+ import { DEFAULT_BUCKET_PRIORITY } from '../BucketDescription.js';
2
+ import { BucketSourceType } from '../BucketSource.js';
3
+ export class SyncStream {
4
+ name;
5
+ subscribedToByDefault;
6
+ priority;
7
+ variants;
8
+ data;
9
+ constructor(name, data) {
10
+ this.name = name;
11
+ this.subscribedToByDefault = false;
12
+ this.priority = DEFAULT_BUCKET_PRIORITY;
13
+ this.variants = [];
14
+ this.data = data;
15
+ }
16
+ get type() {
17
+ return BucketSourceType.SYNC_STREAM;
18
+ }
19
+ pushBucketParameterQueriers(result, options) {
20
+ const subscriptions = options.streams[this.name] ?? [];
21
+ if (!this.subscribedToByDefault && !subscriptions.length) {
22
+ // The client is not subscribing to this stream, so don't query buckets related to it.
23
+ return;
24
+ }
25
+ let hasExplicitDefaultSubscription = false;
26
+ for (const subscription of subscriptions) {
27
+ let subscriptionParams = options.globalParameters;
28
+ if (subscription.parameters != null) {
29
+ subscriptionParams = subscriptionParams.withAddedStreamParameters(subscription.parameters);
30
+ }
31
+ else {
32
+ hasExplicitDefaultSubscription = true;
33
+ }
34
+ this.queriersForSubscription(result, subscription, subscriptionParams, options.bucketIdTransformer);
35
+ }
36
+ // If the stream is subscribed to by default and there is no explicit subscription that would match the default
37
+ // subscription, also include the default querier.
38
+ if (this.subscribedToByDefault && !hasExplicitDefaultSubscription) {
39
+ this.queriersForSubscription(result, null, options.globalParameters, options.bucketIdTransformer);
40
+ }
41
+ }
42
+ queriersForSubscription(result, subscription, params, bucketIdTransformer) {
43
+ const reason = subscription != null ? { subscription: subscription.opaque_id } : 'default';
44
+ const queriers = [];
45
+ try {
46
+ for (const variant of this.variants) {
47
+ const querier = variant.querier(this, reason, params, bucketIdTransformer);
48
+ if (querier) {
49
+ queriers.push(querier);
50
+ }
51
+ }
52
+ result.queriers.push(...queriers);
53
+ }
54
+ catch (e) {
55
+ result.errors.push({
56
+ descriptor: this.name,
57
+ message: `Error evaluating bucket ids: ${e.message}`,
58
+ subscription: subscription ?? undefined
59
+ });
60
+ }
61
+ }
62
+ hasDynamicBucketQueries() {
63
+ return this.variants.some((v) => v.hasDynamicBucketQueries);
64
+ }
65
+ tableSyncsData(table) {
66
+ return this.data.applies(table);
67
+ }
68
+ tableSyncsParameters(table) {
69
+ for (const variant of this.variants) {
70
+ for (const subquery of variant.subqueries) {
71
+ if (subquery.parameterTable.matches(table)) {
72
+ return true;
73
+ }
74
+ }
75
+ }
76
+ return false;
77
+ }
78
+ getSourceTables() {
79
+ let result = new Set();
80
+ result.add(this.data.sourceTable);
81
+ for (let variant of this.variants) {
82
+ for (const subquery of variant.subqueries) {
83
+ result.add(subquery.parameterTable);
84
+ }
85
+ }
86
+ // Note: No physical tables for global_parameter_queries
87
+ return result;
88
+ }
89
+ resolveResultSets(schema, tables) {
90
+ this.data.resolveResultSets(schema, tables);
91
+ }
92
+ debugRepresentation() {
93
+ return {
94
+ name: this.name,
95
+ type: BucketSourceType[this.type],
96
+ variants: this.variants.map((v) => v.debugRepresentation()),
97
+ data: {
98
+ table: this.data.sourceTable,
99
+ columns: this.data.columnOutputNames()
100
+ }
101
+ };
102
+ }
103
+ debugWriteOutputTables(result) {
104
+ result[this.data.table] ??= [];
105
+ const r = {
106
+ query: this.data.sql
107
+ };
108
+ result[this.data.table].push(r);
109
+ }
110
+ evaluateParameterRow(sourceTable, row) {
111
+ const result = [];
112
+ for (const variant of this.variants) {
113
+ variant.pushParameterRowEvaluation(result, sourceTable, row);
114
+ }
115
+ return result;
116
+ }
117
+ evaluateRow(options) {
118
+ if (!this.data.applies(options.sourceTable)) {
119
+ return [];
120
+ }
121
+ const stream = this;
122
+ const row = {
123
+ sourceTable: options.sourceTable,
124
+ record: options.record
125
+ };
126
+ return this.data.evaluateRowWithOptions({
127
+ table: options.sourceTable,
128
+ row: options.record,
129
+ bucketIds() {
130
+ const bucketIds = [];
131
+ for (const variant of stream.variants) {
132
+ bucketIds.push(...variant.bucketIdsForRow(stream.name, row, options.bucketIdTransformer));
133
+ }
134
+ return bucketIds;
135
+ }
136
+ });
137
+ }
138
+ }
139
+ //# sourceMappingURL=stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/streams/stream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function cartesianProduct<T>(...sets: T[][]): Generator<T[]>;
@@ -0,0 +1,13 @@
1
+ export function* cartesianProduct(...sets) {
2
+ if (sets.length == 0) {
3
+ yield [];
4
+ return;
5
+ }
6
+ const [head, ...tail] = sets;
7
+ for (let h of head) {
8
+ const remainder = cartesianProduct(...tail);
9
+ for (let r of remainder)
10
+ yield [h, ...r];
11
+ }
12
+ }
13
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/streams/utils.ts"],"names":[],"mappings":"AAAA,MAAM,SAAS,CAAC,CAAC,gBAAgB,CAAI,GAAG,IAAW;IACjD,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACrB,MAAM,EAAE,CAAC;QACT,OAAO;IACT,CAAC;IAED,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7B,KAAK,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QACnB,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,IAAI,CAAC,CAAC;QAC5C,KAAK,IAAI,CAAC,IAAI,SAAS;YAAE,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC"}
@@ -0,0 +1,122 @@
1
+ import { BucketInclusionReason } from '../BucketDescription.js';
2
+ import { BucketParameterQuerier } from '../BucketParameterQuerier.js';
3
+ import { SourceTableInterface } from '../SourceTableInterface.js';
4
+ import { BucketIdTransformer, EvaluatedParametersResult, RequestParameters, SqliteJsonValue, SqliteRow, TableRow } from '../types.js';
5
+ import { BucketParameter, SubqueryEvaluator } from './parameter.js';
6
+ import { SyncStream } from './stream.js';
7
+ /**
8
+ * A variant of a stream.
9
+ *
10
+ * Variants are introduced on {@link Or} filters, since different sub-filters (with potentially different) bucket
11
+ * parameters can both cause a row to be matched.
12
+ *
13
+ * Consider the query `SELECT * FROM comments WHERE issue_id IN (SELECT id FROM issue WHERE owner_id = request.user()) OR request.is_admin()`.
14
+ * Here, the filter is an or clause matching rows where:
15
+ *
16
+ * - An {@link InOperator} associatates comments in issues owned by the requesting user. This gets implemented with a
17
+ * parameter lookup index mapping `issue.owner_id => issue.id`. `comments.issue_id` is a bucket parameter resolved
18
+ * dynamically.
19
+ * - Or, the user is an admin, in which case all comments are matched. There are no bucket parameters for this
20
+ * variant.
21
+ *
22
+ * The introduction of stream variants allows the `evaluateParameterRow` and `queriersForSubscription` implementations
23
+ * to operate independently.
24
+ *
25
+ * Multiple variants may cause the same row to get synced via different buckets. Depending on the request, users may
26
+ * also receive multiple buckets with the same data. This is not an issue! Clients deduplicate rows received across
27
+ * buckets, so we don't have to filter for this case in the sync service.
28
+ */
29
+ export declare class StreamVariant {
30
+ id: number;
31
+ parameters: BucketParameter[];
32
+ subqueries: SubqueryEvaluator[];
33
+ /**
34
+ * Additional filters that don't introduce bucket parameters, but can exclude rows.
35
+ *
36
+ * This is introduced for streams like `SELECT * FROM assets WHERE LENGTH(assets.name < 10)`.
37
+ */
38
+ additionalRowFilters: ((row: TableRow) => boolean)[];
39
+ /**
40
+ * Additional filters that are evaluated against the request of the stream subscription.
41
+ *
42
+ * These filters can either only depend on values in the request alone (e.g. `WHERE token_parameters.is_admin`), or
43
+ * on results from a subquery (e.g. `WHERE request.user_id() IN (SELECT id FROM user WHERE is_admin)`).
44
+ */
45
+ requestFilters: RequestFilter[];
46
+ constructor(id: number);
47
+ /**
48
+ * Given a row in the table this stream selects from, returns all ids of buckets to which that row belongs to.
49
+ */
50
+ bucketIdsForRow(streamName: string, options: TableRow, transformer: BucketIdTransformer): string[];
51
+ /**
52
+ * Given a row to evaluate, returns all instantiations of parameters that satisfy conditions.
53
+ *
54
+ * The inner arrays will have a length equal to the amount of parameters in this variant.
55
+ */
56
+ instantiationsForRow(options: TableRow): SqliteJsonValue[][];
57
+ /**
58
+ * Turns an array of values for each parameter into an array of all instantiations by effectively building the
59
+ * cartesian product of the parameter sets.
60
+ *
61
+ * @param instantiations An array containing values for each parameter.
62
+ * @returns Each instantiation, with each sub-array having a value for a parameter.
63
+ */
64
+ private cartesianProductOfParameterInstantiations;
65
+ get hasDynamicBucketQueries(): boolean;
66
+ querier(stream: SyncStream, reason: BucketInclusionReason, params: RequestParameters, bucketIdTransformer: BucketIdTransformer): BucketParameterQuerier | null;
67
+ findStaticInstantiations(params: RequestParameters): SqliteJsonValue[][];
68
+ /**
69
+ * Creates lookup indices for dynamically-resolved parameters.
70
+ *
71
+ * Resolving dynamic parameters is a two-step process: First, for tables referenced in subqueries, we create an index
72
+ * to resolve which request parameters would match rows in subqueries. Then, when resolving bucket ids for a request,
73
+ * we compute subquery results by looking up results in that index.
74
+ *
75
+ * This implements the first step of that process.
76
+ *
77
+ * @param result The array into which evaluation results should be written to.
78
+ * @param sourceTable A table we depend on in a subquery.
79
+ * @param row Row data to index.
80
+ */
81
+ pushParameterRowEvaluation(result: EvaluatedParametersResult[], sourceTable: SourceTableInterface, row: SqliteRow): void;
82
+ debugRepresentation(): any;
83
+ /**
84
+ * Replaces {@link StreamVariant.parameters} with static values looked up in request parameters.
85
+ *
86
+ * Dynamic parameters that depend on subquery results are not replaced.
87
+ * This returns null if there's a {@link StaticRequestFilter} that doesn't match the request.
88
+ */
89
+ private partiallyEvaluateParameters;
90
+ /**
91
+ * Builds a bucket id for an instantiation, like `stream|0[1,2,"foo"]`.
92
+ *
93
+ * @param streamName The name of the stream, included in the bucket id
94
+ * @param instantiation An instantiation for all parameters in this variant.
95
+ * @param transformer A transformer adding version information to the inner id.
96
+ * @returns The generated bucket id
97
+ */
98
+ private buildBucketId;
99
+ private resolveBucket;
100
+ }
101
+ /**
102
+ * A stateless filter condition that only depends on the request itself, e.g. `WHERE token_parameters.is_admin`.
103
+ */
104
+ export interface StaticRequestFilter {
105
+ type: 'static';
106
+ matches(params: RequestParameters): boolean;
107
+ }
108
+ /**
109
+ * A filter condition that depends on parameters and an evaluated subquery, e.g.
110
+ * `WHERE request.user_id() IN (SELECT id FROM users WHERE ...)`.
111
+ */
112
+ export interface SubqueryRequestFilter {
113
+ type: 'dynamic';
114
+ subquery: SubqueryEvaluator;
115
+ /**
116
+ * Checks whether the parameter matches values from the subquery.
117
+ *
118
+ * @param results The values that the subquery evaluates to.
119
+ */
120
+ matches(params: RequestParameters, results: SqliteJsonValue[]): boolean;
121
+ }
122
+ export type RequestFilter = StaticRequestFilter | SubqueryRequestFilter;
@@ -0,0 +1,266 @@
1
+ import { isJsonValue, JSONBucketNameSerialize } from '../utils.js';
2
+ import { cartesianProduct } from './utils.js';
3
+ /**
4
+ * A variant of a stream.
5
+ *
6
+ * Variants are introduced on {@link Or} filters, since different sub-filters (with potentially different) bucket
7
+ * parameters can both cause a row to be matched.
8
+ *
9
+ * Consider the query `SELECT * FROM comments WHERE issue_id IN (SELECT id FROM issue WHERE owner_id = request.user()) OR request.is_admin()`.
10
+ * Here, the filter is an or clause matching rows where:
11
+ *
12
+ * - An {@link InOperator} associatates comments in issues owned by the requesting user. This gets implemented with a
13
+ * parameter lookup index mapping `issue.owner_id => issue.id`. `comments.issue_id` is a bucket parameter resolved
14
+ * dynamically.
15
+ * - Or, the user is an admin, in which case all comments are matched. There are no bucket parameters for this
16
+ * variant.
17
+ *
18
+ * The introduction of stream variants allows the `evaluateParameterRow` and `queriersForSubscription` implementations
19
+ * to operate independently.
20
+ *
21
+ * Multiple variants may cause the same row to get synced via different buckets. Depending on the request, users may
22
+ * also receive multiple buckets with the same data. This is not an issue! Clients deduplicate rows received across
23
+ * buckets, so we don't have to filter for this case in the sync service.
24
+ */
25
+ export class StreamVariant {
26
+ id;
27
+ parameters;
28
+ subqueries;
29
+ /**
30
+ * Additional filters that don't introduce bucket parameters, but can exclude rows.
31
+ *
32
+ * This is introduced for streams like `SELECT * FROM assets WHERE LENGTH(assets.name < 10)`.
33
+ */
34
+ additionalRowFilters;
35
+ /**
36
+ * Additional filters that are evaluated against the request of the stream subscription.
37
+ *
38
+ * These filters can either only depend on values in the request alone (e.g. `WHERE token_parameters.is_admin`), or
39
+ * on results from a subquery (e.g. `WHERE request.user_id() IN (SELECT id FROM user WHERE is_admin)`).
40
+ */
41
+ requestFilters;
42
+ constructor(id) {
43
+ this.id = id;
44
+ this.parameters = [];
45
+ this.subqueries = [];
46
+ this.additionalRowFilters = [];
47
+ this.requestFilters = [];
48
+ }
49
+ /**
50
+ * Given a row in the table this stream selects from, returns all ids of buckets to which that row belongs to.
51
+ */
52
+ bucketIdsForRow(streamName, options, transformer) {
53
+ return this.instantiationsForRow(options).map((values) => this.buildBucketId(streamName, values, transformer));
54
+ }
55
+ /**
56
+ * Given a row to evaluate, returns all instantiations of parameters that satisfy conditions.
57
+ *
58
+ * The inner arrays will have a length equal to the amount of parameters in this variant.
59
+ */
60
+ instantiationsForRow(options) {
61
+ for (const additional of this.additionalRowFilters) {
62
+ if (!additional(options)) {
63
+ return [];
64
+ }
65
+ }
66
+ // Contains an array of all values satisfying each parameter. So this array has the same length as the amount of
67
+ // parameters, and each nested array has a dynamic length.
68
+ const parameterInstantiations = [];
69
+ for (const parameter of this.parameters) {
70
+ const matching = parameter.filterRow(options);
71
+ if (matching.length == 0) {
72
+ // The final list of bucket ids is the cartesian product of all matching parameters. So if there's no parameter
73
+ // satisfying this value, we know the final list will be empty.
74
+ return [];
75
+ }
76
+ parameterInstantiations.push(matching);
77
+ }
78
+ // Combine the map of values like {param_1: [foo, bar], param_2: [baz]} into parameter arrays:
79
+ // [foo, baz], [bar, baz].
80
+ return this.cartesianProductOfParameterInstantiations(parameterInstantiations);
81
+ }
82
+ /**
83
+ * Turns an array of values for each parameter into an array of all instantiations by effectively building the
84
+ * cartesian product of the parameter sets.
85
+ *
86
+ * @param instantiations An array containing values for each parameter.
87
+ * @returns Each instantiation, with each sub-array having a value for a parameter.
88
+ */
89
+ cartesianProductOfParameterInstantiations(instantiations) {
90
+ return [...cartesianProduct(...instantiations)];
91
+ }
92
+ get hasDynamicBucketQueries() {
93
+ return this.requestFilters.some((f) => f.type == 'dynamic');
94
+ }
95
+ querier(stream, reason, params, bucketIdTransformer) {
96
+ const instantiation = this.partiallyEvaluateParameters(params);
97
+ if (instantiation == null) {
98
+ return null;
99
+ }
100
+ const dynamicRequestFilters = this.requestFilters.filter((f) => f.type == 'dynamic');
101
+ const dynamicParameters = [];
102
+ const subqueryToLookups = new Map();
103
+ for (let i = 0; i < this.parameters.length; i++) {
104
+ const parameter = this.parameters[i];
105
+ const lookup = parameter.lookup;
106
+ if (lookup.type == 'in' || lookup.type == 'overlap') {
107
+ dynamicParameters.push({
108
+ index: i,
109
+ subquery: lookup.subquery
110
+ });
111
+ }
112
+ }
113
+ for (const subquery of this.subqueries) {
114
+ subqueryToLookups.set(subquery, subquery.lookupsForRequest(params));
115
+ }
116
+ const staticBuckets = [];
117
+ if (dynamicParameters.length == 0 && dynamicRequestFilters.length == 0) {
118
+ // When we have no dynamic parameters, the partial evaluation is a full instantiation.
119
+ const instantiations = this.cartesianProductOfParameterInstantiations(instantiation);
120
+ for (const instantiation of instantiations) {
121
+ staticBuckets.push(this.resolveBucket(stream, instantiation, reason, bucketIdTransformer));
122
+ }
123
+ }
124
+ const variant = this;
125
+ return {
126
+ staticBuckets: staticBuckets,
127
+ hasDynamicBuckets: this.subqueries.length != 0,
128
+ parameterQueryLookups: [...subqueryToLookups.values()].flatMap((f) => f),
129
+ async queryDynamicBucketDescriptions(source) {
130
+ // Evaluate subqueries
131
+ const subqueryResults = new Map();
132
+ for (const [subquery, lookups] of subqueryToLookups.entries()) {
133
+ const rows = await source.getParameterSets(lookups);
134
+ // The result column used in parameter sets is always named result, see pushParameterRowEvaluation
135
+ const values = rows.map((r) => r.result);
136
+ subqueryResults.set(subquery, values);
137
+ }
138
+ // Check if we have a subquery-based request filter rejecting the row.
139
+ for (const filter of dynamicRequestFilters) {
140
+ if (!filter.matches(params, subqueryResults.get(filter.subquery))) {
141
+ return [];
142
+ }
143
+ }
144
+ const perParameterInstantiation = [];
145
+ for (const parameter of instantiation) {
146
+ if (Array.isArray(parameter)) {
147
+ // Statically-resolved values
148
+ perParameterInstantiation.push(parameter);
149
+ }
150
+ else {
151
+ // to be instantiated with dynamic lookup
152
+ perParameterInstantiation.push([parameter]);
153
+ }
154
+ }
155
+ for (const lookup of dynamicParameters) {
156
+ perParameterInstantiation[lookup.index] = subqueryResults.get(lookup.subquery);
157
+ }
158
+ const product = variant.cartesianProductOfParameterInstantiations(perParameterInstantiation);
159
+ return Promise.resolve(product.map((e) => variant.resolveBucket(stream, e, reason, bucketIdTransformer)));
160
+ }
161
+ };
162
+ }
163
+ findStaticInstantiations(params) {
164
+ if (this.subqueries.length) {
165
+ return [];
166
+ }
167
+ // This will be an array of values (i.e. a total evaluation) because there are no dynamic parameters.
168
+ return this.partiallyEvaluateParameters(params);
169
+ }
170
+ /**
171
+ * Creates lookup indices for dynamically-resolved parameters.
172
+ *
173
+ * Resolving dynamic parameters is a two-step process: First, for tables referenced in subqueries, we create an index
174
+ * to resolve which request parameters would match rows in subqueries. Then, when resolving bucket ids for a request,
175
+ * we compute subquery results by looking up results in that index.
176
+ *
177
+ * This implements the first step of that process.
178
+ *
179
+ * @param result The array into which evaluation results should be written to.
180
+ * @param sourceTable A table we depend on in a subquery.
181
+ * @param row Row data to index.
182
+ */
183
+ pushParameterRowEvaluation(result, sourceTable, row) {
184
+ for (const subquery of this.subqueries) {
185
+ if (subquery.parameterTable.matches(sourceTable)) {
186
+ const lookups = subquery.lookupsForParameterRow(sourceTable, row);
187
+ if (lookups == null) {
188
+ continue;
189
+ }
190
+ // The row of the subquery. Since we only support subqueries with a single column, we unconditionally name the
191
+ // column `result` for simplicity.
192
+ const resultRow = { result: lookups.value };
193
+ result.push(...lookups.lookups.map((l) => ({
194
+ lookup: l,
195
+ bucketParameters: [resultRow]
196
+ })));
197
+ }
198
+ }
199
+ }
200
+ debugRepresentation() {
201
+ return {
202
+ id: this.id,
203
+ parameters: this.parameters.map((p) => ({
204
+ type: p.lookup.type
205
+ })),
206
+ subqueries: this.subqueries.map((s) => ({
207
+ table: s.parameterTable
208
+ })),
209
+ additional_row_filters: this.additionalRowFilters.length,
210
+ request_filters: this.requestFilters.map((f) => f.type)
211
+ };
212
+ }
213
+ /**
214
+ * Replaces {@link StreamVariant.parameters} with static values looked up in request parameters.
215
+ *
216
+ * Dynamic parameters that depend on subquery results are not replaced.
217
+ * This returns null if there's a {@link StaticRequestFilter} that doesn't match the request.
218
+ */
219
+ partiallyEvaluateParameters(params) {
220
+ for (const filter of this.requestFilters) {
221
+ if (filter.type == 'static' && !filter.matches(params)) {
222
+ return null;
223
+ }
224
+ }
225
+ const instantiation = [];
226
+ for (const parameter of this.parameters) {
227
+ const lookup = parameter.lookup;
228
+ if (lookup.type == 'static') {
229
+ const values = lookup.fromRequest(params)?.filter(isJsonValue);
230
+ if (values.length == 0) {
231
+ // Parameter not instantiable for this request. Since parameters in a single variant form a conjunction, that
232
+ // means the whole request won't find anything here.
233
+ return null;
234
+ }
235
+ instantiation.push(values);
236
+ }
237
+ else {
238
+ instantiation.push(parameter);
239
+ }
240
+ }
241
+ return instantiation;
242
+ }
243
+ /**
244
+ * Builds a bucket id for an instantiation, like `stream|0[1,2,"foo"]`.
245
+ *
246
+ * @param streamName The name of the stream, included in the bucket id
247
+ * @param instantiation An instantiation for all parameters in this variant.
248
+ * @param transformer A transformer adding version information to the inner id.
249
+ * @returns The generated bucket id
250
+ */
251
+ buildBucketId(streamName, instantiation, transformer) {
252
+ if (instantiation.length != this.parameters.length) {
253
+ throw Error('Internal error, instantiation length mismatch');
254
+ }
255
+ return transformer(`${streamName}|${this.id}${JSONBucketNameSerialize.stringify(instantiation)}`);
256
+ }
257
+ resolveBucket(stream, instantiation, reason, bucketIdTransformer) {
258
+ return {
259
+ definition: stream.name,
260
+ inclusion_reasons: [reason],
261
+ bucket: this.buildBucketId(stream.name, instantiation, bucketIdTransformer),
262
+ priority: stream.priority
263
+ };
264
+ }
265
+ }
266
+ //# sourceMappingURL=variant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variant.js","sourceRoot":"","sources":["../../src/streams/variant.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { CompatibilityContext } from '../compatibility.js';
2
+ import { SqliteValue } from '../types.js';
3
+ import { SqliteValueType } from '../ExpressionType.js';
4
+ /**
5
+ * A value that decays into a {@link SqliteValue} in a context-specific way.
6
+ *
7
+ * This is used to conditionally render some values in different formats depending on compatibility options. For
8
+ * instance, old versions of the sync service used to [encode timestamp values incorrectly](https://github.com/powersync-ja/powersync-service/issues/286).
9
+ * To fix this without breaking backwards-compatibility, we now represent timestamp values as a {@link CustomSqliteValue}
10
+ * subtype where `toSqliteValue` returns the old or the new format depending on options.
11
+ *
12
+ * Instances of {@link CustomSqliteValue} are always temporary structures that aren't persisted. They are created by the
13
+ * replicator implementations, the sync rule implementation will invoke {@link toSqliteValue} to ensure that an
14
+ * {@link EvaluatedRow} only consists of proper SQLite values.
15
+ */
16
+ export declare abstract class CustomSqliteValue {
17
+ /**
18
+ * Renders this custom value into a {@link SqliteValue}.
19
+ *
20
+ * @param context The current compatibility options.
21
+ */
22
+ abstract toSqliteValue(context: CompatibilityContext): SqliteValue;
23
+ abstract get sqliteType(): SqliteValueType;
24
+ }
25
+ export declare class CustomArray extends CustomSqliteValue {
26
+ private readonly elements;
27
+ private readonly map;
28
+ constructor(elements: any[], map: (element: any, context: CompatibilityContext) => void);
29
+ get sqliteType(): SqliteValueType;
30
+ toSqliteValue(context: CompatibilityContext): SqliteValue;
31
+ }
32
+ export declare class CustomObject extends CustomSqliteValue {
33
+ private readonly source;
34
+ private readonly map;
35
+ constructor(source: Record<string, any>, map: (element: any, context: CompatibilityContext) => void);
36
+ get sqliteType(): SqliteValueType;
37
+ toSqliteValue(context: CompatibilityContext): SqliteValue;
38
+ }
@@ -0,0 +1,50 @@
1
+ import { JSONBig } from '@powersync/service-jsonbig';
2
+ /**
3
+ * A value that decays into a {@link SqliteValue} in a context-specific way.
4
+ *
5
+ * This is used to conditionally render some values in different formats depending on compatibility options. For
6
+ * instance, old versions of the sync service used to [encode timestamp values incorrectly](https://github.com/powersync-ja/powersync-service/issues/286).
7
+ * To fix this without breaking backwards-compatibility, we now represent timestamp values as a {@link CustomSqliteValue}
8
+ * subtype where `toSqliteValue` returns the old or the new format depending on options.
9
+ *
10
+ * Instances of {@link CustomSqliteValue} are always temporary structures that aren't persisted. They are created by the
11
+ * replicator implementations, the sync rule implementation will invoke {@link toSqliteValue} to ensure that an
12
+ * {@link EvaluatedRow} only consists of proper SQLite values.
13
+ */
14
+ export class CustomSqliteValue {
15
+ }
16
+ export class CustomArray extends CustomSqliteValue {
17
+ elements;
18
+ map;
19
+ constructor(elements, map) {
20
+ super();
21
+ this.elements = elements;
22
+ this.map = map;
23
+ }
24
+ get sqliteType() {
25
+ return 'text';
26
+ }
27
+ toSqliteValue(context) {
28
+ return JSONBig.stringify(this.elements.map((element) => this.map(element, context)));
29
+ }
30
+ }
31
+ export class CustomObject extends CustomSqliteValue {
32
+ source;
33
+ map;
34
+ constructor(source, map) {
35
+ super();
36
+ this.source = source;
37
+ this.map = map;
38
+ }
39
+ get sqliteType() {
40
+ return 'text';
41
+ }
42
+ toSqliteValue(context) {
43
+ let record = {};
44
+ for (let key of Object.keys(this.source)) {
45
+ record[key] = this.map(this.source[key], context);
46
+ }
47
+ return JSONBig.stringify(record);
48
+ }
49
+ }
50
+ //# sourceMappingURL=custom_sqlite_value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom_sqlite_value.js","sourceRoot":"","sources":["../../src/types/custom_sqlite_value.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAKrD;;;;;;;;;;;GAWG;AACH,MAAM,OAAgB,iBAAiB;CAStC;AAED,MAAM,OAAO,WAAY,SAAQ,iBAAiB;IAE7B;IACA;IAFnB,YACmB,QAAe,EACf,GAA0D;QAE3E,KAAK,EAAE,CAAC;QAHS,aAAQ,GAAR,QAAQ,CAAO;QACf,QAAG,GAAH,GAAG,CAAuD;IAG7E,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,aAAa,CAAC,OAA6B;QACzC,OAAO,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACvF,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,iBAAiB;IAE9B;IACA;IAFnB,YACmB,MAA2B,EAC3B,GAA0D;QAE3E,KAAK,EAAE,CAAC;QAHS,WAAM,GAAN,MAAM,CAAqB;QAC3B,QAAG,GAAH,GAAG,CAAuD;IAG7E,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,aAAa,CAAC,OAA6B;QACzC,IAAI,MAAM,GAAwB,EAAE,CAAC;QACrC,KAAK,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACzC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;CACF"}