@hops-ops/distributed 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +454 -0
- package/dist/auth-headers.d.ts +8 -0
- package/dist/auth-headers.js +35 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +1 -0
- package/dist/document.d.ts +8 -0
- package/dist/document.js +6 -0
- package/dist/identity.d.ts +23 -0
- package/dist/identity.js +73 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/internal/cache-engine/create.d.ts +5 -0
- package/dist/internal/cache-engine/create.js +7 -0
- package/dist/internal/cache-engine/engine.d.ts +32 -0
- package/dist/internal/cache-engine/engine.js +1287 -0
- package/dist/internal/cache-engine/errors.d.ts +5 -0
- package/dist/internal/cache-engine/errors.js +10 -0
- package/dist/internal/cache-engine/helpers.d.ts +55 -0
- package/dist/internal/cache-engine/helpers.js +545 -0
- package/dist/internal/cache-engine/index.d.ts +3 -0
- package/dist/internal/cache-engine/index.js +2 -0
- package/dist/internal/cache-engine/types.d.ts +300 -0
- package/dist/internal/cache-engine/types.js +1 -0
- package/dist/internal/cache-engine.d.ts +3 -0
- package/dist/internal/cache-engine.js +2 -0
- package/dist/lib/assert-name.d.ts +2 -0
- package/dist/lib/assert-name.js +6 -0
- package/dist/lib/compare-code-units.d.ts +2 -0
- package/dist/lib/compare-code-units.js +4 -0
- package/dist/lib/deep-equal.d.ts +2 -0
- package/dist/lib/deep-equal.js +24 -0
- package/dist/lib/freeze-record.d.ts +2 -0
- package/dist/lib/freeze-record.js +13 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/is-plain-record.d.ts +2 -0
- package/dist/lib/is-plain-record.js +8 -0
- package/dist/lib/report.d.ts +7 -0
- package/dist/lib/report.js +26 -0
- package/dist/protocol.d.ts +179 -0
- package/dist/protocol.js +558 -0
- package/dist/react/context.d.ts +20 -0
- package/dist/react/context.js +24 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +3 -0
- package/dist/react/query.d.ts +20 -0
- package/dist/react/query.js +99 -0
- package/dist/replica/command-id.d.ts +2 -0
- package/dist/replica/command-id.js +19 -0
- package/dist/replica/command-runtime/constants.d.ts +5 -0
- package/dist/replica/command-runtime/constants.js +5 -0
- package/dist/replica/command-runtime/create.d.ts +2 -0
- package/dist/replica/command-runtime/create.js +644 -0
- package/dist/replica/command-runtime/errors.d.ts +16 -0
- package/dist/replica/command-runtime/errors.js +41 -0
- package/dist/replica/command-runtime/index.d.ts +5 -0
- package/dist/replica/command-runtime/index.js +4 -0
- package/dist/replica/command-runtime/lib/binding.d.ts +4 -0
- package/dist/replica/command-runtime/lib/binding.js +64 -0
- package/dist/replica/command-runtime/lib/effects.d.ts +11 -0
- package/dist/replica/command-runtime/lib/effects.js +89 -0
- package/dist/replica/command-runtime/lib/index.d.ts +9 -0
- package/dist/replica/command-runtime/lib/index.js +9 -0
- package/dist/replica/command-runtime/lib/inventory.d.ts +10 -0
- package/dist/replica/command-runtime/lib/inventory.js +141 -0
- package/dist/replica/command-runtime/lib/output.d.ts +13 -0
- package/dist/replica/command-runtime/lib/output.js +190 -0
- package/dist/replica/command-runtime/lib/projection.d.ts +20 -0
- package/dist/replica/command-runtime/lib/projection.js +255 -0
- package/dist/replica/command-runtime/lib/status.d.ts +7 -0
- package/dist/replica/command-runtime/lib/status.js +127 -0
- package/dist/replica/command-runtime/lib/transport.d.ts +16 -0
- package/dist/replica/command-runtime/lib/transport.js +112 -0
- package/dist/replica/command-runtime/lib/util.d.ts +16 -0
- package/dist/replica/command-runtime/lib/util.js +127 -0
- package/dist/replica/command-runtime/lifecycle.d.ts +11 -0
- package/dist/replica/command-runtime/lifecycle.js +13 -0
- package/dist/replica/command-runtime/symbols.d.ts +30 -0
- package/dist/replica/command-runtime/symbols.js +30 -0
- package/dist/replica/command-runtime/types.d.ts +222 -0
- package/dist/replica/command-runtime/types.js +1 -0
- package/dist/replica/command-runtime.d.ts +3 -0
- package/dist/replica/command-runtime.js +2 -0
- package/dist/replica/commands/clone.d.ts +12 -0
- package/dist/replica/commands/clone.js +187 -0
- package/dist/replica/commands/constants.d.ts +6 -0
- package/dist/replica/commands/constants.js +6 -0
- package/dist/replica/commands/errors.d.ts +7 -0
- package/dist/replica/commands/errors.js +22 -0
- package/dist/replica/commands/implementation.d.ts +8 -0
- package/dist/replica/commands/implementation.js +8 -0
- package/dist/replica/commands/index.d.ts +5 -0
- package/dist/replica/commands/index.js +4 -0
- package/dist/replica/commands/prepare.d.ts +27 -0
- package/dist/replica/commands/prepare.js +77 -0
- package/dist/replica/commands/presets.d.ts +12 -0
- package/dist/replica/commands/presets.js +84 -0
- package/dist/replica/commands/receipt.d.ts +3 -0
- package/dist/replica/commands/receipt.js +36 -0
- package/dist/replica/commands/resolve.d.ts +11 -0
- package/dist/replica/commands/resolve.js +165 -0
- package/dist/replica/commands/types.d.ts +278 -0
- package/dist/replica/commands/types.js +2 -0
- package/dist/replica/commands/util.d.ts +21 -0
- package/dist/replica/commands/util.js +96 -0
- package/dist/replica/commands/validate.d.ts +18 -0
- package/dist/replica/commands/validate.js +477 -0
- package/dist/replica/commands.d.ts +3 -0
- package/dist/replica/commands.js +2 -0
- package/dist/replica/diagnostics/event-log.d.ts +4 -0
- package/dist/replica/diagnostics/event-log.js +463 -0
- package/dist/replica/diagnostics/implementation.d.ts +4 -0
- package/dist/replica/diagnostics/implementation.js +4 -0
- package/dist/replica/diagnostics/index.d.ts +2 -0
- package/dist/replica/diagnostics/index.js +1 -0
- package/dist/replica/diagnostics/inspect.d.ts +6 -0
- package/dist/replica/diagnostics/inspect.js +170 -0
- package/dist/replica/diagnostics/types.d.ts +295 -0
- package/dist/replica/diagnostics/types.js +2 -0
- package/dist/replica/diagnostics.d.ts +3 -0
- package/dist/replica/diagnostics.js +2 -0
- package/dist/replica/distributed-replica/clocks.d.ts +23 -0
- package/dist/replica/distributed-replica/clocks.js +156 -0
- package/dist/replica/distributed-replica/constants.d.ts +12 -0
- package/dist/replica/distributed-replica/constants.js +10 -0
- package/dist/replica/distributed-replica/helpers.d.ts +48 -0
- package/dist/replica/distributed-replica/helpers.js +445 -0
- package/dist/replica/distributed-replica/hydration.d.ts +19 -0
- package/dist/replica/distributed-replica/hydration.js +380 -0
- package/dist/replica/distributed-replica/impl-diagnostics.d.ts +22 -0
- package/dist/replica/distributed-replica/impl-diagnostics.js +189 -0
- package/dist/replica/distributed-replica/impl-fetch-live.d.ts +39 -0
- package/dist/replica/distributed-replica/impl-fetch-live.js +395 -0
- package/dist/replica/distributed-replica/impl-hydration-orchestrate.d.ts +56 -0
- package/dist/replica/distributed-replica/impl-hydration-orchestrate.js +299 -0
- package/dist/replica/distributed-replica/impl-optimistic.d.ts +31 -0
- package/dist/replica/distributed-replica/impl-optimistic.js +172 -0
- package/dist/replica/distributed-replica/impl-protocol.d.ts +53 -0
- package/dist/replica/distributed-replica/impl-protocol.js +108 -0
- package/dist/replica/distributed-replica/impl.d.ts +44 -0
- package/dist/replica/distributed-replica/impl.js +1776 -0
- package/dist/replica/distributed-replica/index.d.ts +2 -0
- package/dist/replica/distributed-replica/index.js +4 -0
- package/dist/replica/distributed-replica/optimistic.d.ts +30 -0
- package/dist/replica/distributed-replica/optimistic.js +221 -0
- package/dist/replica/distributed-replica/types.d.ts +147 -0
- package/dist/replica/distributed-replica/types.js +1 -0
- package/dist/replica/distributed-replica/watch.d.ts +19 -0
- package/dist/replica/distributed-replica/watch.js +95 -0
- package/dist/replica/distributed-replica.d.ts +2 -0
- package/dist/replica/distributed-replica.js +2 -0
- package/dist/replica/graphql-transport.d.ts +24 -0
- package/dist/replica/graphql-transport.js +122 -0
- package/dist/replica/identity/clone.d.ts +6 -0
- package/dist/replica/identity/clone.js +54 -0
- package/dist/replica/identity/codec.d.ts +48 -0
- package/dist/replica/identity/codec.js +789 -0
- package/dist/replica/identity/constants.d.ts +3 -0
- package/dist/replica/identity/constants.js +18 -0
- package/dist/replica/identity/implementation.d.ts +5 -0
- package/dist/replica/identity/implementation.js +5 -0
- package/dist/replica/identity/index.d.ts +3 -0
- package/dist/replica/identity/index.js +3 -0
- package/dist/replica/identity/keys.d.ts +29 -0
- package/dist/replica/identity/keys.js +173 -0
- package/dist/replica/identity.d.ts +2 -0
- package/dist/replica/identity.js +2 -0
- package/dist/replica/index-maintenance/engine.d.ts +84 -0
- package/dist/replica/index-maintenance/engine.js +677 -0
- package/dist/replica/index-maintenance/implementation.d.ts +4 -0
- package/dist/replica/index-maintenance/implementation.js +4 -0
- package/dist/replica/index-maintenance/index.d.ts +2 -0
- package/dist/replica/index-maintenance/index.js +1 -0
- package/dist/replica/index-maintenance/registry.d.ts +2 -0
- package/dist/replica/index-maintenance/registry.js +49 -0
- package/dist/replica/index-maintenance/types.d.ts +58 -0
- package/dist/replica/index-maintenance/types.js +1 -0
- package/dist/replica/index-maintenance.d.ts +3 -0
- package/dist/replica/index-maintenance.js +2 -0
- package/dist/replica/index.d.ts +17 -0
- package/dist/replica/index.js +9 -0
- package/dist/replica/materialize.d.ts +10 -0
- package/dist/replica/materialize.js +163 -0
- package/dist/replica/normalize.d.ts +27 -0
- package/dist/replica/normalize.js +298 -0
- package/dist/replica/operation-binding.d.ts +14 -0
- package/dist/replica/operation-binding.js +76 -0
- package/dist/replica/persistence/idb.d.ts +20 -0
- package/dist/replica/persistence/idb.js +193 -0
- package/dist/replica/persistence/implementation.d.ts +4 -0
- package/dist/replica/persistence/implementation.js +4 -0
- package/dist/replica/persistence/index.d.ts +2 -0
- package/dist/replica/persistence/index.js +1 -0
- package/dist/replica/persistence/state.d.ts +104 -0
- package/dist/replica/persistence/state.js +734 -0
- package/dist/replica/persistence/types.d.ts +54 -0
- package/dist/replica/persistence/types.js +1 -0
- package/dist/replica/persistence.d.ts +3 -0
- package/dist/replica/persistence.js +2 -0
- package/dist/replica/query-plan/constants.d.ts +13 -0
- package/dist/replica/query-plan/constants.js +7 -0
- package/dist/replica/query-plan/filter.d.ts +39 -0
- package/dist/replica/query-plan/filter.js +544 -0
- package/dist/replica/query-plan/index.d.ts +4 -0
- package/dist/replica/query-plan/index.js +3 -0
- package/dist/replica/query-plan/order.d.ts +11 -0
- package/dist/replica/query-plan/order.js +158 -0
- package/dist/replica/query-plan/pagination.d.ts +6 -0
- package/dist/replica/query-plan/pagination.js +90 -0
- package/dist/replica/query-plan/resolve.d.ts +14 -0
- package/dist/replica/query-plan/resolve.js +163 -0
- package/dist/replica/query-plan/types.d.ts +89 -0
- package/dist/replica/query-plan/types.js +1 -0
- package/dist/replica/query-plan/util.d.ts +13 -0
- package/dist/replica/query-plan/util.js +62 -0
- package/dist/replica/query-plan.d.ts +3 -0
- package/dist/replica/query-plan.js +2 -0
- package/dist/replica/revalidation.d.ts +9 -0
- package/dist/replica/revalidation.js +86 -0
- package/dist/replica/selection.d.ts +20 -0
- package/dist/replica/selection.js +109 -0
- package/dist/replica/types.d.ts +657 -0
- package/dist/replica/types.js +1 -0
- package/dist/request.d.ts +17 -0
- package/dist/request.js +123 -0
- package/dist/sveltekit/auth.d.ts +18 -0
- package/dist/sveltekit/auth.js +9 -0
- package/dist/sveltekit/context.d.ts +28 -0
- package/dist/sveltekit/context.js +58 -0
- package/dist/sveltekit/index.d.ts +4 -0
- package/dist/sveltekit/index.js +4 -0
- package/dist/sveltekit/replica.d.ts +145 -0
- package/dist/sveltekit/replica.js +491 -0
- package/dist/sveltekit/server-replica.d.ts +52 -0
- package/dist/sveltekit/server-replica.js +176 -0
- package/dist/sveltekit/vite.d.ts +103 -0
- package/dist/sveltekit/vite.js +878 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +1 -0
- package/dist/websocket.d.ts +41 -0
- package/dist/websocket.js +187 -0
- package/package.json +95 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { ReplicaFilterEvaluation, ReplicaFilterEvaluationOptions, ReplicaOrderComparison, ReplicaPaginationChange, ReplicaPaginationMaintenanceDecision, ReplicaQueryPlanPath, ReplicaQueryPlanReason, ReplicaQueryPlanReasonCode, ReplicaRelationshipFilterRequest } from './types.js';
|
|
2
|
+
export { evaluateReplicaFilter } from './filter.js';
|
|
3
|
+
export { compareReplicaOrder } from './order.js';
|
|
4
|
+
export { decideReplicaPaginationMaintenance } from './pagination.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { GraphqlVariables } from '../../types.js';
|
|
2
|
+
import type { ReplicaOrderArtifact, ReplicaValue } from '../types.js';
|
|
3
|
+
import type { OrderDirection, ReplicaOrderComparison, ReplicaQueryPlanPath, ReplicaQueryPlanReason } from './types.js';
|
|
4
|
+
export declare function compareReplicaOrder(artifact: ReplicaOrderArtifact, left: Readonly<Record<string, ReplicaValue>>, right: Readonly<Record<string, ReplicaValue>>, variables?: GraphqlVariables): ReplicaOrderComparison;
|
|
5
|
+
export declare function compareOrderField(field: string, codec: string, nullable: boolean, direction: OrderDirection, left: Readonly<Record<string, ReplicaValue>>, right: Readonly<Record<string, ReplicaValue>>, path: ReplicaQueryPlanPath): ReplicaOrderComparison;
|
|
6
|
+
export declare function compareUtf8(left: string, right: string): -1 | 0 | 1;
|
|
7
|
+
export declare function orderUnknown(reasonValue: ReplicaQueryPlanReason): ReplicaOrderComparison;
|
|
8
|
+
export declare function orderResult(comparison: -1 | 1): ReplicaOrderComparison;
|
|
9
|
+
export declare function isOrderDirection(value: ReplicaValue): value is OrderDirection;
|
|
10
|
+
export declare function hasExplicitNullPlacement(direction: OrderDirection): boolean;
|
|
11
|
+
export declare function invert(value: -1 | 1): -1 | 1;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { ORDER_EQUAL, UTF8_ENCODER } from './constants.js';
|
|
2
|
+
import { recordField, resolveInput } from './resolve.js';
|
|
3
|
+
import { isName, isPortableNumericCodec, isRecord, isScalarCodecPair, reason } from './util.js';
|
|
4
|
+
import { validateComparableValues } from './filter.js';
|
|
5
|
+
export function compareReplicaOrder(artifact, left, right, variables = {}) {
|
|
6
|
+
const fields = new Map();
|
|
7
|
+
for (const [index, field] of artifact.fields.entries()) {
|
|
8
|
+
if (!isName(field.field) ||
|
|
9
|
+
!isScalarCodecPair(field.scalar, field.codec) ||
|
|
10
|
+
typeof field.nullable !== 'boolean' ||
|
|
11
|
+
fields.has(field.field)) {
|
|
12
|
+
return orderUnknown(reason('invalid_artifact', ['order', 'fields', index], 'order fields must have unique names, exact scalar-codec pairs, and nullability'));
|
|
13
|
+
}
|
|
14
|
+
fields.set(field.field, field);
|
|
15
|
+
}
|
|
16
|
+
const tieBreakerFields = new Set();
|
|
17
|
+
for (const [index, tieBreaker] of artifact.tieBreakers.entries()) {
|
|
18
|
+
if (!isName(tieBreaker.field) ||
|
|
19
|
+
!isScalarCodecPair(tieBreaker.scalar, tieBreaker.codec) ||
|
|
20
|
+
tieBreaker.nullable !== false ||
|
|
21
|
+
tieBreakerFields.has(tieBreaker.field)) {
|
|
22
|
+
return orderUnknown(reason('invalid_artifact', ['order', 'tieBreakers', index], 'order tie-breakers must have unique fields and exact scalar-codec pairs'));
|
|
23
|
+
}
|
|
24
|
+
tieBreakerFields.add(tieBreaker.field);
|
|
25
|
+
}
|
|
26
|
+
const input = resolveInput(artifact.input, variables, ['order_by']);
|
|
27
|
+
if (input.kind === 'unknown')
|
|
28
|
+
return orderUnknown(input.reason);
|
|
29
|
+
const entries = input.kind === 'omitted' || input.value === null
|
|
30
|
+
? []
|
|
31
|
+
: Array.isArray(input.value)
|
|
32
|
+
? input.value
|
|
33
|
+
: isRecord(input.value)
|
|
34
|
+
? [input.value]
|
|
35
|
+
: [];
|
|
36
|
+
if (input.kind === 'value' &&
|
|
37
|
+
input.value !== null &&
|
|
38
|
+
!Array.isArray(input.value) &&
|
|
39
|
+
!isRecord(input.value)) {
|
|
40
|
+
return orderUnknown(reason('invalid_order_input', ['order_by'], 'order_by must be an object or a list of objects'));
|
|
41
|
+
}
|
|
42
|
+
for (const [index, entry] of entries.entries()) {
|
|
43
|
+
const path = ['order_by', index];
|
|
44
|
+
if (!isRecord(entry)) {
|
|
45
|
+
return orderUnknown(reason('invalid_order_input', path, 'each order_by entry must be an object'));
|
|
46
|
+
}
|
|
47
|
+
const pairs = Object.entries(entry);
|
|
48
|
+
if (pairs.length === 0)
|
|
49
|
+
continue;
|
|
50
|
+
if (pairs.length > 1) {
|
|
51
|
+
return orderUnknown(reason('ambiguous_order_entry', path, 'each order_by entry may declare at most one field'));
|
|
52
|
+
}
|
|
53
|
+
const [fieldName, rawDirection] = pairs[0];
|
|
54
|
+
const field = fields.get(fieldName);
|
|
55
|
+
if (field === undefined) {
|
|
56
|
+
return orderUnknown(reason('unknown_field', [...path, fieldName], `order field ${fieldName} is not declared by the artifact`));
|
|
57
|
+
}
|
|
58
|
+
if (!isOrderDirection(rawDirection)) {
|
|
59
|
+
return orderUnknown(reason('invalid_order_direction', [...path, fieldName], `order direction for ${fieldName} is not supported`));
|
|
60
|
+
}
|
|
61
|
+
if (field.nullable && !hasExplicitNullPlacement(rawDirection)) {
|
|
62
|
+
return orderUnknown(reason('implicit_null_order', [...path, fieldName], `nullable order field ${fieldName} requires explicit null placement`));
|
|
63
|
+
}
|
|
64
|
+
const compared = compareOrderField(field.field, field.codec, field.nullable, rawDirection, left, right, [...path, fieldName]);
|
|
65
|
+
if (compared.result !== 'equal')
|
|
66
|
+
return compared;
|
|
67
|
+
}
|
|
68
|
+
for (const [index, tieBreaker] of artifact.tieBreakers.entries()) {
|
|
69
|
+
const compared = compareOrderField(tieBreaker.field, tieBreaker.codec, false, 'asc', left, right, ['order', 'tieBreakers', index, tieBreaker.field]);
|
|
70
|
+
if (compared.result !== 'equal')
|
|
71
|
+
return compared;
|
|
72
|
+
}
|
|
73
|
+
if (artifact.tieBreakers.length === 0) {
|
|
74
|
+
return orderUnknown(reason('invalid_artifact', ['order', 'tieBreakers'], 'equal order values require a complete identity tie-breaker'));
|
|
75
|
+
}
|
|
76
|
+
return ORDER_EQUAL;
|
|
77
|
+
}
|
|
78
|
+
export function compareOrderField(field, codec, nullable, direction, left, right, path) {
|
|
79
|
+
const a = recordField(left, field, path);
|
|
80
|
+
if (a.kind === 'unknown')
|
|
81
|
+
return orderUnknown(a.reason);
|
|
82
|
+
const b = recordField(right, field, path);
|
|
83
|
+
if (b.kind === 'unknown')
|
|
84
|
+
return orderUnknown(b.reason);
|
|
85
|
+
if ((a.value === null || b.value === null) && !nullable) {
|
|
86
|
+
return orderUnknown(reason('invalid_order_value', path, `non-null order field ${field} contains null`));
|
|
87
|
+
}
|
|
88
|
+
if (a.value === null || b.value === null) {
|
|
89
|
+
if (a.value === null && b.value === null)
|
|
90
|
+
return ORDER_EQUAL;
|
|
91
|
+
if (!hasExplicitNullPlacement(direction)) {
|
|
92
|
+
return orderUnknown(reason('implicit_null_order', path, `nullable order field ${field} requires explicit null placement`));
|
|
93
|
+
}
|
|
94
|
+
const nullFirst = direction.endsWith('_nulls_first');
|
|
95
|
+
return orderResult((a.value === null) === nullFirst ? -1 : 1);
|
|
96
|
+
}
|
|
97
|
+
const validity = validateComparableValues(codec, a.value, b.value, path);
|
|
98
|
+
if (validity !== undefined)
|
|
99
|
+
return orderUnknown(validity);
|
|
100
|
+
let comparison;
|
|
101
|
+
if (isPortableNumericCodec(codec)) {
|
|
102
|
+
comparison =
|
|
103
|
+
a.value < b.value
|
|
104
|
+
? -1
|
|
105
|
+
: a.value > b.value
|
|
106
|
+
? 1
|
|
107
|
+
: 0;
|
|
108
|
+
}
|
|
109
|
+
else if (codec === 'boolean') {
|
|
110
|
+
comparison =
|
|
111
|
+
a.value === b.value ? 0 : a.value === false && b.value === true ? -1 : 1;
|
|
112
|
+
}
|
|
113
|
+
else if (codec === 'string') {
|
|
114
|
+
comparison = compareUtf8(a.value, b.value);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
return orderUnknown(reason('unsupported_codec', path, `codec ${codec} has no certified replica comparator`));
|
|
118
|
+
}
|
|
119
|
+
if (comparison === 0)
|
|
120
|
+
return ORDER_EQUAL;
|
|
121
|
+
return orderResult(direction.startsWith('desc') ? invert(comparison) : comparison);
|
|
122
|
+
}
|
|
123
|
+
export function compareUtf8(left, right) {
|
|
124
|
+
if (left === right)
|
|
125
|
+
return 0;
|
|
126
|
+
const leftBytes = UTF8_ENCODER.encode(left);
|
|
127
|
+
const rightBytes = UTF8_ENCODER.encode(right);
|
|
128
|
+
const length = Math.min(leftBytes.length, rightBytes.length);
|
|
129
|
+
for (let index = 0; index < length; index += 1) {
|
|
130
|
+
const a = leftBytes[index];
|
|
131
|
+
const b = rightBytes[index];
|
|
132
|
+
if (a !== b)
|
|
133
|
+
return a < b ? -1 : 1;
|
|
134
|
+
}
|
|
135
|
+
return leftBytes.length < rightBytes.length ? -1 : 1;
|
|
136
|
+
}
|
|
137
|
+
export function orderUnknown(reasonValue) {
|
|
138
|
+
return Object.freeze({ result: 'unknown', reason: reasonValue });
|
|
139
|
+
}
|
|
140
|
+
export function orderResult(comparison) {
|
|
141
|
+
return Object.freeze({
|
|
142
|
+
result: comparison < 0 ? 'less' : 'greater'
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
export function isOrderDirection(value) {
|
|
146
|
+
return (value === 'asc' ||
|
|
147
|
+
value === 'asc_nulls_first' ||
|
|
148
|
+
value === 'asc_nulls_last' ||
|
|
149
|
+
value === 'desc' ||
|
|
150
|
+
value === 'desc_nulls_first' ||
|
|
151
|
+
value === 'desc_nulls_last');
|
|
152
|
+
}
|
|
153
|
+
export function hasExplicitNullPlacement(direction) {
|
|
154
|
+
return direction.includes('_nulls_');
|
|
155
|
+
}
|
|
156
|
+
export function invert(value) {
|
|
157
|
+
return value === -1 ? 1 : -1;
|
|
158
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ReplicaIndexCoverage, ReplicaPaginationArtifact, ReplicaPaginationDisposition } from '../types.js';
|
|
2
|
+
import type { ReplicaPaginationChange, ReplicaPaginationMaintenanceDecision, ReplicaQueryPlanReason } from './types.js';
|
|
3
|
+
export declare function decideReplicaPaginationMaintenance(artifact: ReplicaPaginationArtifact, coverage: ReplicaIndexCoverage, change: ReplicaPaginationChange): ReplicaPaginationMaintenanceDecision;
|
|
4
|
+
export declare function paginationRevalidate(reasonValue: ReplicaQueryPlanReason): ReplicaPaginationMaintenanceDecision;
|
|
5
|
+
export declare function validatePaginationPolicies(artifact: ReplicaPaginationArtifact): ReplicaQueryPlanReason | undefined;
|
|
6
|
+
export declare function policyForChange(artifact: ReplicaPaginationArtifact, change: ReplicaPaginationChange): ReplicaPaginationDisposition;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { PAGINATION_LOCAL } from './constants.js';
|
|
2
|
+
import { reason } from './util.js';
|
|
3
|
+
export function decideReplicaPaginationMaintenance(artifact, coverage, change) {
|
|
4
|
+
const invalidPolicy = validatePaginationPolicies(artifact);
|
|
5
|
+
if (invalidPolicy !== undefined)
|
|
6
|
+
return paginationRevalidate(invalidPolicy);
|
|
7
|
+
if (coverage.kind === 'unknown') {
|
|
8
|
+
return paginationRevalidate(reason('unknown_coverage', ['pagination', 'coverage'], 'unknown index coverage cannot be maintained locally'));
|
|
9
|
+
}
|
|
10
|
+
if (coverage.kind !== artifact.kind) {
|
|
11
|
+
return paginationRevalidate(reason('coverage_mismatch', ['pagination', 'coverage'], `artifact coverage ${artifact.kind} does not match index coverage ${coverage.kind}`));
|
|
12
|
+
}
|
|
13
|
+
if (artifact.kind === 'cursor') {
|
|
14
|
+
return paginationRevalidate(reason('cursor_not_certified', ['pagination', 'kind'], 'cursor locality requires a versioned compiler proof IR'));
|
|
15
|
+
}
|
|
16
|
+
const policy = policyForChange(artifact, change);
|
|
17
|
+
if (policy === 'local') {
|
|
18
|
+
if (artifact.kind !== 'offset' ||
|
|
19
|
+
change.kind === 'stable_update') {
|
|
20
|
+
return PAGINATION_LOCAL;
|
|
21
|
+
}
|
|
22
|
+
if (coverage.kind === 'offset' &&
|
|
23
|
+
coverage.offset === 0 &&
|
|
24
|
+
coverage.limit !== undefined &&
|
|
25
|
+
coverage.returned !== undefined &&
|
|
26
|
+
coverage.returned < coverage.limit) {
|
|
27
|
+
// A non-full first page proves that the server returned the complete
|
|
28
|
+
// ordered set. The index runtime can therefore apply all optimistic
|
|
29
|
+
// membership/order changes and truncate back to the declared limit.
|
|
30
|
+
return PAGINATION_LOCAL;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const [code, message] = change.kind === 'insert'
|
|
34
|
+
? [
|
|
35
|
+
'insert_changes_offset_window',
|
|
36
|
+
'an insert is local only for a proven non-full first offset page'
|
|
37
|
+
]
|
|
38
|
+
: change.kind === 'delete'
|
|
39
|
+
? [
|
|
40
|
+
'delete_changes_offset_window',
|
|
41
|
+
'a delete is local only for a proven non-full first offset page'
|
|
42
|
+
]
|
|
43
|
+
: change.kind === 'reorder'
|
|
44
|
+
? [
|
|
45
|
+
'reorder_changes_offset_window',
|
|
46
|
+
'a reorder is local only for a proven non-full first offset page'
|
|
47
|
+
]
|
|
48
|
+
: [
|
|
49
|
+
'invalid_pagination_policy',
|
|
50
|
+
'the artifact requires stable updates to revalidate'
|
|
51
|
+
];
|
|
52
|
+
return paginationRevalidate(reason(code, ['pagination', change.kind], message));
|
|
53
|
+
}
|
|
54
|
+
export function paginationRevalidate(reasonValue) {
|
|
55
|
+
return Object.freeze({
|
|
56
|
+
decision: 'revalidate',
|
|
57
|
+
reason: reasonValue
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export function validatePaginationPolicies(artifact) {
|
|
61
|
+
if (artifact.kind !== 'complete' &&
|
|
62
|
+
artifact.kind !== 'offset' &&
|
|
63
|
+
artifact.kind !== 'cursor') {
|
|
64
|
+
return reason('invalid_pagination_policy', ['pagination', 'kind'], 'pagination kind must be complete, offset, or cursor');
|
|
65
|
+
}
|
|
66
|
+
const values = [
|
|
67
|
+
['insert', artifact.insert],
|
|
68
|
+
['delete', artifact.delete],
|
|
69
|
+
['reorder', artifact.reorder],
|
|
70
|
+
['stableUpdate', artifact.stableUpdate]
|
|
71
|
+
];
|
|
72
|
+
for (const [field, value] of values) {
|
|
73
|
+
if (value !== 'local' && value !== 'revalidate') {
|
|
74
|
+
return reason('invalid_pagination_policy', ['pagination', field], `pagination policy ${field} must be local or revalidate`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const exact = artifact.kind === 'complete'
|
|
78
|
+
? values.every(([, value]) => value === 'local')
|
|
79
|
+
: artifact.kind === 'offset'
|
|
80
|
+
? artifact.stableUpdate === 'local'
|
|
81
|
+
: true;
|
|
82
|
+
return exact
|
|
83
|
+
? undefined
|
|
84
|
+
: reason('invalid_pagination_policy', ['pagination'], `${artifact.kind} pagination policies do not match the conservative compiler contract`);
|
|
85
|
+
}
|
|
86
|
+
export function policyForChange(artifact, change) {
|
|
87
|
+
return change.kind === 'stable_update'
|
|
88
|
+
? artifact.stableUpdate
|
|
89
|
+
: artifact[change.kind];
|
|
90
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { GraphqlVariables } from '../../types.js';
|
|
2
|
+
import type { ReplicaArgumentValue, ReplicaFilterFieldArtifact, ReplicaFilterOperand, ReplicaValue } from '../types.js';
|
|
3
|
+
import type { ReplicaFilterEvaluationOptions, ReplicaQueryPlanPath, ReplicaQueryPlanReason, ResolvedInput, ResolvedOperand } from './types.js';
|
|
4
|
+
export declare function resolveInput(input: ReplicaArgumentValue | undefined, variables: GraphqlVariables, path: ReplicaQueryPlanPath): ResolvedInput;
|
|
5
|
+
export declare function resolveOperand(operand: ReplicaFilterOperand, field: ReplicaFilterFieldArtifact, options: ReplicaFilterEvaluationOptions, path: ReplicaQueryPlanPath): ResolvedOperand;
|
|
6
|
+
export declare function resolveTrustedPresetOperand(name: string, field: ReplicaFilterFieldArtifact, options: ReplicaFilterEvaluationOptions, path: ReplicaQueryPlanPath): ResolvedOperand;
|
|
7
|
+
export declare function recordField(record: Readonly<Record<string, ReplicaValue>>, field: string, path: ReplicaQueryPlanPath): {
|
|
8
|
+
readonly kind: 'value';
|
|
9
|
+
readonly value: ReplicaValue;
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: 'unknown';
|
|
12
|
+
readonly reason: ReplicaQueryPlanReason;
|
|
13
|
+
};
|
|
14
|
+
export declare function uniqueStringList(value: unknown): readonly string[] | undefined;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { resolveReplicaArgumentValue } from '../identity.js';
|
|
2
|
+
import { isFiniteNumber, isName, isRecord, isReplicaValue, reason } from './util.js';
|
|
3
|
+
export function resolveInput(input, variables, path) {
|
|
4
|
+
if (input === undefined)
|
|
5
|
+
return { kind: 'omitted' };
|
|
6
|
+
try {
|
|
7
|
+
const value = resolveReplicaArgumentValue(input, variables);
|
|
8
|
+
return value === undefined
|
|
9
|
+
? { kind: 'omitted' }
|
|
10
|
+
: { kind: 'value', value };
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return {
|
|
14
|
+
kind: 'unknown',
|
|
15
|
+
reason: reason('invalid_argument_value', path, error instanceof Error
|
|
16
|
+
? error.message
|
|
17
|
+
: 'plan input could not be resolved')
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function resolveOperand(operand, field, options, path) {
|
|
22
|
+
if (operand.kind === 'claim') {
|
|
23
|
+
return resolveTrustedPresetOperand(operand.value.header, field, options, path);
|
|
24
|
+
}
|
|
25
|
+
if (operand.kind !== 'lit' || !isRecord(operand.value)) {
|
|
26
|
+
return {
|
|
27
|
+
kind: 'unknown',
|
|
28
|
+
reason: reason('invalid_artifact', path, 'row-policy operand is malformed')
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const literal = operand.value;
|
|
32
|
+
if (literal.kind === 'null') {
|
|
33
|
+
return {
|
|
34
|
+
kind: 'value',
|
|
35
|
+
value: null,
|
|
36
|
+
literalKind: literal.kind,
|
|
37
|
+
source: 'literal'
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (literal.kind === 'string' &&
|
|
41
|
+
typeof literal.value === 'string') {
|
|
42
|
+
return {
|
|
43
|
+
kind: 'value',
|
|
44
|
+
value: literal.value,
|
|
45
|
+
literalKind: literal.kind,
|
|
46
|
+
source: 'literal'
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (literal.kind === 'bool' && typeof literal.value === 'boolean') {
|
|
50
|
+
return {
|
|
51
|
+
kind: 'value',
|
|
52
|
+
value: literal.value,
|
|
53
|
+
literalKind: literal.kind,
|
|
54
|
+
source: 'literal'
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if ((literal.kind === 'i64' && Number.isSafeInteger(literal.value)) ||
|
|
58
|
+
(literal.kind === 'f64' && isFiniteNumber(literal.value))) {
|
|
59
|
+
return {
|
|
60
|
+
kind: 'value',
|
|
61
|
+
value: literal.value,
|
|
62
|
+
literalKind: literal.kind,
|
|
63
|
+
source: 'literal'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (literal.kind === 'json' && isReplicaValue(literal.value)) {
|
|
67
|
+
return {
|
|
68
|
+
kind: 'value',
|
|
69
|
+
value: literal.value,
|
|
70
|
+
literalKind: literal.kind,
|
|
71
|
+
source: 'literal'
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
kind: 'unknown',
|
|
76
|
+
reason: reason('invalid_artifact', path, 'row-policy literal is not a portable JSON value')
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function resolveTrustedPresetOperand(name, field, options, path) {
|
|
80
|
+
const inventory = options.trustedPresets;
|
|
81
|
+
if (inventory === undefined) {
|
|
82
|
+
return {
|
|
83
|
+
kind: 'unknown',
|
|
84
|
+
reason: reason('claim_operand', path, `claim ${name} has no authoritative scope-bound preset inventory`)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const descriptors = new Map();
|
|
88
|
+
for (const [index, descriptor] of inventory.descriptors.entries()) {
|
|
89
|
+
if (typeof descriptor?.name !== 'string' ||
|
|
90
|
+
descriptor.name.length === 0 ||
|
|
91
|
+
typeof descriptor.codec !== 'string' ||
|
|
92
|
+
descriptors.has(descriptor.name)) {
|
|
93
|
+
return {
|
|
94
|
+
kind: 'unknown',
|
|
95
|
+
reason: reason('claim_inventory', [...path, 'descriptors', index], 'trusted preset descriptor inventory is malformed or duplicated')
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
descriptors.set(descriptor.name, descriptor);
|
|
99
|
+
}
|
|
100
|
+
const values = new Map();
|
|
101
|
+
for (const [index, preset] of inventory.values.entries()) {
|
|
102
|
+
if (typeof preset?.name !== 'string' ||
|
|
103
|
+
preset.name.length === 0 ||
|
|
104
|
+
typeof preset.codec !== 'string' ||
|
|
105
|
+
values.has(preset.name)) {
|
|
106
|
+
return {
|
|
107
|
+
kind: 'unknown',
|
|
108
|
+
reason: reason('claim_inventory', [...path, 'values', index], 'trusted preset value inventory is malformed or duplicated')
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
values.set(preset.name, preset);
|
|
112
|
+
}
|
|
113
|
+
if (descriptors.size !== values.size ||
|
|
114
|
+
[...descriptors].some(([presetName, descriptor]) => values.get(presetName)?.codec !== descriptor.codec)) {
|
|
115
|
+
return {
|
|
116
|
+
kind: 'unknown',
|
|
117
|
+
reason: reason('claim_inventory', path, 'trusted preset descriptors and scope-bound values do not form an exact union')
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const descriptor = descriptors.get(name);
|
|
121
|
+
const preset = values.get(name);
|
|
122
|
+
if (descriptor === undefined || preset === undefined) {
|
|
123
|
+
return {
|
|
124
|
+
kind: 'unknown',
|
|
125
|
+
reason: reason('claim_operand', path, `claim ${name} is absent from the selected client surface`)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (descriptor.codec !== field.codec) {
|
|
129
|
+
return {
|
|
130
|
+
kind: 'unknown',
|
|
131
|
+
reason: reason('claim_operand', path, `claim ${name} codec ${descriptor.codec} cannot target ${field.field}:${field.codec}`)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (!isReplicaValue(preset.value)) {
|
|
135
|
+
return {
|
|
136
|
+
kind: 'unknown',
|
|
137
|
+
reason: reason('claim_inventory', path, `claim ${name} is not a portable replica value`)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
kind: 'value',
|
|
142
|
+
value: preset.value,
|
|
143
|
+
source: 'trusted_preset'
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export function recordField(record, field, path) {
|
|
147
|
+
if (!Object.prototype.hasOwnProperty.call(record, field)) {
|
|
148
|
+
return {
|
|
149
|
+
kind: 'unknown',
|
|
150
|
+
reason: reason('missing_field', path, `record does not contain canonical field ${field}`)
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return { kind: 'value', value: record[field] };
|
|
154
|
+
}
|
|
155
|
+
export function uniqueStringList(value) {
|
|
156
|
+
if (!Array.isArray(value) || value.some((entry) => !isName(entry))) {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
const values = value;
|
|
160
|
+
if (new Set(values).size !== values.length)
|
|
161
|
+
return undefined;
|
|
162
|
+
return Object.freeze([...values]);
|
|
163
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { DistributedTrustedPreset } from '../../protocol.js';
|
|
2
|
+
import type { ReplicaFilterExpression, ReplicaFilterFieldArtifact, ReplicaFilterLiteral, ReplicaRelationshipArtifact, ReplicaSurfaceTrustedPresetDescriptor, ReplicaValue } from '../types.js';
|
|
3
|
+
export type ReplicaQueryPlanPath = readonly (string | number)[];
|
|
4
|
+
export type ReplicaQueryPlanReasonCode = 'ambiguous_order_entry' | 'claim_operand' | 'claim_inventory' | 'collation_not_portable' | 'coverage_mismatch' | 'cursor_not_certified' | 'delete_changes_offset_window' | 'implicit_null_order' | 'invalid_argument_value' | 'insert_changes_offset_window' | 'invalid_artifact' | 'invalid_filter_input' | 'invalid_filter_value' | 'invalid_order_direction' | 'invalid_order_input' | 'invalid_order_value' | 'invalid_pagination_policy' | 'missing_field' | 'relationship_resolver_required' | 'reorder_changes_offset_window' | 'server_only_policy' | 'sql_null' | 'unknown_coverage' | 'unknown_field' | 'unknown_relationship' | 'unsupported_codec' | 'unsupported_operator';
|
|
5
|
+
export type ReplicaQueryPlanReason = {
|
|
6
|
+
readonly code: ReplicaQueryPlanReasonCode;
|
|
7
|
+
readonly path: ReplicaQueryPlanPath;
|
|
8
|
+
readonly message: string;
|
|
9
|
+
};
|
|
10
|
+
export type ReplicaFilterEvaluation = {
|
|
11
|
+
readonly result: 'match' | 'no_match';
|
|
12
|
+
} | {
|
|
13
|
+
readonly result: 'unknown';
|
|
14
|
+
readonly reason: ReplicaQueryPlanReason;
|
|
15
|
+
};
|
|
16
|
+
export type ReplicaRelationshipFilterRequest = {
|
|
17
|
+
readonly source: 'caller';
|
|
18
|
+
readonly relationship: ReplicaRelationshipArtifact;
|
|
19
|
+
readonly predicate: ReplicaValue;
|
|
20
|
+
readonly record: Readonly<Record<string, ReplicaValue>>;
|
|
21
|
+
readonly path: ReplicaQueryPlanPath;
|
|
22
|
+
} | {
|
|
23
|
+
readonly source: 'row_policy';
|
|
24
|
+
readonly relationship: ReplicaRelationshipArtifact;
|
|
25
|
+
readonly predicate: ReplicaFilterExpression;
|
|
26
|
+
readonly record: Readonly<Record<string, ReplicaValue>>;
|
|
27
|
+
readonly path: ReplicaQueryPlanPath;
|
|
28
|
+
};
|
|
29
|
+
export type ReplicaFilterEvaluationOptions = {
|
|
30
|
+
/**
|
|
31
|
+
* Resolves the server's EXISTS semantics for a relationship predicate.
|
|
32
|
+
* The resolver is responsible for the target model's row policy as well as
|
|
33
|
+
* the supplied predicate; omitting it deliberately produces `unknown`.
|
|
34
|
+
*/
|
|
35
|
+
readonly resolveRelationship?: (request: ReplicaRelationshipFilterRequest) => ReplicaFilterEvaluation;
|
|
36
|
+
/**
|
|
37
|
+
* Static compiler-owned descriptor union paired with the current
|
|
38
|
+
* server-derived, cache-scope-bound values. The evaluator never reads
|
|
39
|
+
* ambient token claims or caller headers.
|
|
40
|
+
*/
|
|
41
|
+
readonly trustedPresets?: {
|
|
42
|
+
readonly descriptors: readonly ReplicaSurfaceTrustedPresetDescriptor[];
|
|
43
|
+
readonly values: readonly DistributedTrustedPreset[];
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export type ReplicaOrderComparison = {
|
|
47
|
+
readonly result: 'less' | 'equal' | 'greater';
|
|
48
|
+
} | {
|
|
49
|
+
readonly result: 'unknown';
|
|
50
|
+
readonly reason: ReplicaQueryPlanReason;
|
|
51
|
+
};
|
|
52
|
+
export type ReplicaPaginationChange = {
|
|
53
|
+
readonly kind: 'insert';
|
|
54
|
+
} | {
|
|
55
|
+
readonly kind: 'delete';
|
|
56
|
+
} | {
|
|
57
|
+
readonly kind: 'reorder';
|
|
58
|
+
} | {
|
|
59
|
+
readonly kind: 'stable_update';
|
|
60
|
+
};
|
|
61
|
+
export type ReplicaPaginationMaintenanceDecision = {
|
|
62
|
+
readonly decision: 'local';
|
|
63
|
+
} | {
|
|
64
|
+
readonly decision: 'revalidate';
|
|
65
|
+
readonly reason: ReplicaQueryPlanReason;
|
|
66
|
+
};
|
|
67
|
+
export type FilterCatalog = {
|
|
68
|
+
readonly fields: ReadonlyMap<string, ReplicaFilterFieldArtifact>;
|
|
69
|
+
readonly relationships: ReadonlyMap<string, ReplicaRelationshipArtifact>;
|
|
70
|
+
};
|
|
71
|
+
export type ResolvedInput = {
|
|
72
|
+
readonly kind: 'omitted';
|
|
73
|
+
} | {
|
|
74
|
+
readonly kind: 'value';
|
|
75
|
+
readonly value: ReplicaValue;
|
|
76
|
+
} | {
|
|
77
|
+
readonly kind: 'unknown';
|
|
78
|
+
readonly reason: ReplicaQueryPlanReason;
|
|
79
|
+
};
|
|
80
|
+
export type ResolvedOperand = {
|
|
81
|
+
readonly kind: 'value';
|
|
82
|
+
readonly value: ReplicaValue;
|
|
83
|
+
readonly literalKind?: ReplicaFilterLiteral['kind'];
|
|
84
|
+
readonly source: 'literal' | 'trusted_preset';
|
|
85
|
+
} | {
|
|
86
|
+
readonly kind: 'unknown';
|
|
87
|
+
readonly reason: ReplicaQueryPlanReason;
|
|
88
|
+
};
|
|
89
|
+
export type OrderDirection = 'asc' | 'asc_nulls_first' | 'asc_nulls_last' | 'desc' | 'desc_nulls_first' | 'desc_nulls_last';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReplicaValue } from '../types.js';
|
|
2
|
+
import type { ReplicaQueryPlanPath, ReplicaQueryPlanReason, ReplicaQueryPlanReasonCode } from './types.js';
|
|
3
|
+
export declare function reason(code: ReplicaQueryPlanReasonCode, path: ReplicaQueryPlanPath, message: string): ReplicaQueryPlanReason;
|
|
4
|
+
export declare function isScalarCodecPair(scalar: unknown, codec: unknown): boolean;
|
|
5
|
+
export declare function isPortableNumericCodec(codec: string): boolean;
|
|
6
|
+
export declare function isSafeIntegerNumber(value: ReplicaValue): value is number;
|
|
7
|
+
export declare function isInt32(value: ReplicaValue): value is number;
|
|
8
|
+
export declare function isFiniteNumber(value: unknown): value is number;
|
|
9
|
+
export declare function isName(value: unknown): value is string;
|
|
10
|
+
export declare function isRecord(value: ReplicaValue | unknown): value is {
|
|
11
|
+
readonly [key: string]: ReplicaValue;
|
|
12
|
+
};
|
|
13
|
+
export declare function isReplicaValue(value: unknown, ancestors?: Set<object>): value is ReplicaValue;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function reason(code, path, message) {
|
|
2
|
+
return Object.freeze({
|
|
3
|
+
code,
|
|
4
|
+
path: Object.freeze([...path]),
|
|
5
|
+
message
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
export function isScalarCodecPair(scalar, codec) {
|
|
9
|
+
if (typeof scalar !== 'string' || typeof codec !== 'string')
|
|
10
|
+
return false;
|
|
11
|
+
return (`${scalar}:${codec}` === 'ID:string' ||
|
|
12
|
+
`${scalar}:${codec}` === 'String:string' ||
|
|
13
|
+
`${scalar}:${codec}` === 'Bytea:base64' ||
|
|
14
|
+
`${scalar}:${codec}` ===
|
|
15
|
+
'Timestamptz:string_unvalidated_timestamp' ||
|
|
16
|
+
`${scalar}:${codec}` === 'Boolean:boolean' ||
|
|
17
|
+
`${scalar}:${codec}` === 'Int:int32' ||
|
|
18
|
+
`${scalar}:${codec}` === 'Float:float64' ||
|
|
19
|
+
`${scalar}:${codec}` ===
|
|
20
|
+
'BigInt:json_number_precision_limited' ||
|
|
21
|
+
`${scalar}:${codec}` === 'JSON:json');
|
|
22
|
+
}
|
|
23
|
+
export function isPortableNumericCodec(codec) {
|
|
24
|
+
return (codec === 'int32' ||
|
|
25
|
+
codec === 'float64' ||
|
|
26
|
+
codec === 'json_number_precision_limited');
|
|
27
|
+
}
|
|
28
|
+
export function isSafeIntegerNumber(value) {
|
|
29
|
+
return typeof value === 'number' && Number.isSafeInteger(value);
|
|
30
|
+
}
|
|
31
|
+
export function isInt32(value) {
|
|
32
|
+
return (Number.isInteger(value) &&
|
|
33
|
+
value >= -2_147_483_648 &&
|
|
34
|
+
value <= 2_147_483_647);
|
|
35
|
+
}
|
|
36
|
+
export function isFiniteNumber(value) {
|
|
37
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
38
|
+
}
|
|
39
|
+
export function isName(value) {
|
|
40
|
+
return typeof value === 'string' && value.length > 0;
|
|
41
|
+
}
|
|
42
|
+
export function isRecord(value) {
|
|
43
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
export function isReplicaValue(value, ancestors = new Set()) {
|
|
46
|
+
if (value === null ||
|
|
47
|
+
typeof value === 'string' ||
|
|
48
|
+
typeof value === 'boolean' ||
|
|
49
|
+
isFiniteNumber(value)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (typeof value !== 'object' || ancestors.has(value))
|
|
53
|
+
return false;
|
|
54
|
+
ancestors.add(value);
|
|
55
|
+
const valid = Array.isArray(value)
|
|
56
|
+
? value.every((entry) => isReplicaValue(entry, ancestors))
|
|
57
|
+
: (Object.getPrototypeOf(value) === Object.prototype ||
|
|
58
|
+
Object.getPrototypeOf(value) === null) &&
|
|
59
|
+
Object.values(value).every((entry) => isReplicaValue(entry, ancestors));
|
|
60
|
+
ancestors.delete(value);
|
|
61
|
+
return valid;
|
|
62
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Query plan evaluation; implementation lives in ./query-plan/. */
|
|
2
|
+
export { compareReplicaOrder, decideReplicaPaginationMaintenance, evaluateReplicaFilter } from './query-plan/index.js';
|
|
3
|
+
export type { ReplicaFilterEvaluation, ReplicaFilterEvaluationOptions, ReplicaOrderComparison, ReplicaPaginationChange, ReplicaPaginationMaintenanceDecision, ReplicaQueryPlanPath, ReplicaQueryPlanReason, ReplicaQueryPlanReasonCode, ReplicaRelationshipFilterRequest } from './query-plan/index.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { GraphqlVariables } from '../types.js';
|
|
2
|
+
import type { ReplicaOperationArtifact, ReplicaRevalidationPlan } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Compile one validated command inventory into a reusable operation matcher.
|
|
5
|
+
*
|
|
6
|
+
* Adapters only forward generated plans; the replica owns knowledge of active
|
|
7
|
+
* operation artifacts and therefore remains the framework-neutral coordinator.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createReplicaRevalidationMatcher(plan: ReplicaRevalidationPlan): (artifact: ReplicaOperationArtifact<unknown, GraphqlVariables>) => boolean;
|