@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
package/README.md
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
# `@hops-ops/distributed`
|
|
2
|
+
|
|
3
|
+
The generated, end-to-end typed client for
|
|
4
|
+
[Distributed](https://github.com/hops-ops/distributed) services.
|
|
5
|
+
|
|
6
|
+
Rust table, relationship, role, and command definitions produce one authorized
|
|
7
|
+
client surface. `dctl client` combines that surface with application GraphQL
|
|
8
|
+
documents and emits typed operations, live companions, route-load plans, and
|
|
9
|
+
commands. This package executes those artifacts through one normalized,
|
|
10
|
+
causally consistent browser replica.
|
|
11
|
+
|
|
12
|
+
The intended application experience is:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
const todos = Todos.use();
|
|
16
|
+
const commands = useCommands();
|
|
17
|
+
await commands.todo.create({ title });
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Reads populate the replica on demand. Every UI consumer reads that same
|
|
21
|
+
replica. Generated optimistic command effects update it synchronously, and
|
|
22
|
+
server-issued record/index clocks reconcile the authoritative result without
|
|
23
|
+
application-authored cache policies.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @hops-ops/distributed graphql
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The package is ESM-only and requires Node 20 or newer for server-side use. It
|
|
32
|
+
also runs in modern browsers. Svelte 5 and React are optional peers used only
|
|
33
|
+
by the `/sveltekit` and `/react` entry points, respectively.
|
|
34
|
+
|
|
35
|
+
## Generate the client surface
|
|
36
|
+
|
|
37
|
+
The service, not the browser, owns authorization and GraphQL semantics:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
dctl client-manifest > target/distributed-client.json
|
|
41
|
+
|
|
42
|
+
dctl client \
|
|
43
|
+
--manifest target/distributed-client.json \
|
|
44
|
+
--role user \
|
|
45
|
+
--documents 'src/**/*.graphql' \
|
|
46
|
+
--out src/lib/generated/distributed
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use `--surface <name>` for a named application surface. CI can append `--check`
|
|
50
|
+
to validate that committed artifacts are current without rewriting them.
|
|
51
|
+
|
|
52
|
+
A co-located route document can opt into SSR and live continuation:
|
|
53
|
+
|
|
54
|
+
```graphql
|
|
55
|
+
query Todos @load @live {
|
|
56
|
+
todos(order_by: [{ status: asc }, { todo_id: asc }]) {
|
|
57
|
+
todo_id
|
|
58
|
+
title
|
|
59
|
+
status
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Generation validates the document against the selected role/application,
|
|
65
|
+
injects wire-only identity and revision fields, and emits:
|
|
66
|
+
|
|
67
|
+
- an exact typed operation and optional live companion;
|
|
68
|
+
- normalization, identity, relationship, filter, order, and pagination plans;
|
|
69
|
+
- the closed variable codec used before cache lookup or transport;
|
|
70
|
+
- a static `@load` route registry;
|
|
71
|
+
- an SSR-safe SvelteKit wrapper with static operation bindings and tree-local
|
|
72
|
+
client/command access;
|
|
73
|
+
- a nested command tree with input defaults, optimistic effects, and causal
|
|
74
|
+
confirmation contracts;
|
|
75
|
+
- an exact schema, protocol, and client-surface binding.
|
|
76
|
+
|
|
77
|
+
Unsupported or unprovable behavior fails during generation. The runtime does
|
|
78
|
+
not parse GraphQL documents, guess cache keys, or infer mutation effects.
|
|
79
|
+
|
|
80
|
+
## SvelteKit
|
|
81
|
+
|
|
82
|
+
Install Svelte and describe each generated authorization surface once:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npm install svelte
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
// distributed.config.js
|
|
90
|
+
const serviceManifestArgs = [
|
|
91
|
+
'client-manifest',
|
|
92
|
+
'--manifest-path',
|
|
93
|
+
'../service/Cargo.toml',
|
|
94
|
+
'--package',
|
|
95
|
+
'service'
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
export const distributedClients = [
|
|
99
|
+
{
|
|
100
|
+
module: '$distributed',
|
|
101
|
+
manifest: { args: serviceManifestArgs },
|
|
102
|
+
surface: 'fieldnote',
|
|
103
|
+
documents: ['src/routes/(app)/**/*.graphql'],
|
|
104
|
+
out: 'src/lib/generated/distributed'
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
module: '$distributed/admin',
|
|
108
|
+
manifest: {
|
|
109
|
+
args: [
|
|
110
|
+
...serviceManifestArgs,
|
|
111
|
+
'--entrypoint',
|
|
112
|
+
'service::distributed_admin_client_surface'
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
surface: 'fieldnote-admin',
|
|
116
|
+
documents: ['src/routes/admin/**/*.graphql'],
|
|
117
|
+
out: 'src/lib/generated/distributed-admin'
|
|
118
|
+
}
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
export const distributedViteOptions = {
|
|
122
|
+
clients: distributedClients
|
|
123
|
+
};
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The common and elevated document sets must not overlap. The route group above
|
|
127
|
+
keeps ordinary application documents out of the admin tree; each trust boundary
|
|
128
|
+
has its own Rust manifest entrypoint, generated directory, virtual module, and
|
|
129
|
+
request-local replica. A single-surface application can omit the second entry.
|
|
130
|
+
|
|
131
|
+
The Vite integration runs `dctl client` at startup/build, watches GraphQL
|
|
132
|
+
documents, stages all surfaces, commits a rollback-capable multi-output
|
|
133
|
+
transaction, then triggers one reload. It exposes the generated Svelte wrapper
|
|
134
|
+
through the configured virtual module:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
// vite.config.ts
|
|
138
|
+
import { sveltekit } from '@sveltejs/kit/vite';
|
|
139
|
+
import {
|
|
140
|
+
distributedGraphqlProxy,
|
|
141
|
+
distributedSvelteKit
|
|
142
|
+
} from '@hops-ops/distributed/sveltekit/vite';
|
|
143
|
+
import { defineConfig } from 'vite';
|
|
144
|
+
import { distributedViteOptions } from './distributed.config.js';
|
|
145
|
+
|
|
146
|
+
export default defineConfig({
|
|
147
|
+
plugins: [distributedSvelteKit(distributedViteOptions), sveltekit()],
|
|
148
|
+
server: {
|
|
149
|
+
proxy: distributedGraphqlProxy('http://127.0.0.1:8791')
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Give SvelteKit’s language tools the identical aliases:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
// svelte.config.js
|
|
158
|
+
import {
|
|
159
|
+
distributedSvelteKitAliases
|
|
160
|
+
} from '@hops-ops/distributed/sveltekit/vite';
|
|
161
|
+
import {
|
|
162
|
+
distributedClients,
|
|
163
|
+
distributedViteOptions
|
|
164
|
+
} from './distributed.config.js';
|
|
165
|
+
|
|
166
|
+
export default {
|
|
167
|
+
kit: {
|
|
168
|
+
alias: distributedSvelteKitAliases({
|
|
169
|
+
cwd: distributedViteOptions.cwd,
|
|
170
|
+
clients: distributedClients
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
One-shot scripts use the same configuration and transaction:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
import {
|
|
180
|
+
checkDistributedSvelteKit,
|
|
181
|
+
generateDistributedSvelteKit
|
|
182
|
+
} from '@hops-ops/distributed/sveltekit/vite';
|
|
183
|
+
import { distributedViteOptions } from './distributed.config.js';
|
|
184
|
+
|
|
185
|
+
await generateDistributedSvelteKit(distributedViteOptions);
|
|
186
|
+
await checkDistributedSvelteKit(distributedViteOptions); // never writes
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Create one request-local server replica in the root layout:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
// src/routes/+layout.server.ts
|
|
193
|
+
import {
|
|
194
|
+
createDistributedSvelteKitServer
|
|
195
|
+
} from '@hops-ops/distributed/sveltekit';
|
|
196
|
+
import {
|
|
197
|
+
DISTRIBUTED_ROUTE_OPERATIONS
|
|
198
|
+
} from '$distributed';
|
|
199
|
+
|
|
200
|
+
const distributed = createDistributedSvelteKitServer({
|
|
201
|
+
routes: DISTRIBUTED_ROUTE_OPERATIONS,
|
|
202
|
+
getSession: ({ locals }) => locals.auth(),
|
|
203
|
+
getRole: (session) => roleFromSession(session)
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
export const load = distributed.load;
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The browser layout installs one client in Svelte context for the current
|
|
210
|
+
authorization lifecycle. The generated module retains no client singleton:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
// src/routes/+layout.svelte
|
|
214
|
+
import { browser } from '$app/environment';
|
|
215
|
+
import {
|
|
216
|
+
createPageDataSessionSource
|
|
217
|
+
} from '@hops-ops/distributed/sveltekit';
|
|
218
|
+
import { provideDistributed } from '$distributed';
|
|
219
|
+
|
|
220
|
+
let { data, children } = $props();
|
|
221
|
+
const pageData = createPageDataSessionSource(data);
|
|
222
|
+
|
|
223
|
+
const client = provideDistributed({
|
|
224
|
+
browser,
|
|
225
|
+
session: pageData.session,
|
|
226
|
+
...(data.distributed !== undefined &&
|
|
227
|
+
data.distributedAuthority !== undefined
|
|
228
|
+
? {
|
|
229
|
+
hydration: data.distributed,
|
|
230
|
+
authority: data.distributedAuthority
|
|
231
|
+
}
|
|
232
|
+
: {})
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
$effect(() => pageData.set(data));
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Route components import only their generated surface. Static operation wrappers
|
|
239
|
+
resolve the nearest tree-local client when used:
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
// src/routes/todos/+page.svelte
|
|
243
|
+
import { Todos, useCommands } from '$distributed';
|
|
244
|
+
|
|
245
|
+
const todos = Todos.use(); // generated @live attaches automatically
|
|
246
|
+
const commands = useCommands();
|
|
247
|
+
|
|
248
|
+
await commands.todo.create({ title: 'Ship it' });
|
|
249
|
+
// $todos.data, $todos.status, $todos.pending
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
When the Rust command declaration supplies a UUIDv7, ULID, or literal input
|
|
253
|
+
default, generation makes that field optional and the runtime fills it exactly
|
|
254
|
+
once. Components do not generate IDs or maintain optimistic/cache recipes.
|
|
255
|
+
|
|
256
|
+
`@load` results are normalized on the server, dehydrated, and restored in the
|
|
257
|
+
browser without a duplicate first request. Hydration cannot authorize itself:
|
|
258
|
+
the server sends a separate authority value, and the adapter requires both
|
|
259
|
+
values to match. Session, token, tenant, or role changes abort HTTP and live
|
|
260
|
+
work, discard the old generation, and reconnect under server-issued scope.
|
|
261
|
+
|
|
262
|
+
Use a separate generated surface and replica for elevated routes. A normal
|
|
263
|
+
client cannot import or mix admin artifacts. Configure it as a separate virtual
|
|
264
|
+
module such as `$distributed/admin` and provide it only in the elevated layout.
|
|
265
|
+
|
|
266
|
+
## Framework-neutral replica
|
|
267
|
+
|
|
268
|
+
Other frameworks can bind the same core directly:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import {
|
|
272
|
+
createDistributedReplica,
|
|
273
|
+
createReplicaGraphqlTransport
|
|
274
|
+
} from '@hops-ops/distributed/replica';
|
|
275
|
+
import { Operation_Todos } from './generated/distributed/index.js';
|
|
276
|
+
|
|
277
|
+
const transport = createReplicaGraphqlTransport({
|
|
278
|
+
getUrl: () => '/graphql',
|
|
279
|
+
getAuth: () => ({ accessToken: session.accessToken })
|
|
280
|
+
});
|
|
281
|
+
const replica = createDistributedReplica({ transport });
|
|
282
|
+
const todos = replica.watch(Operation_Todos, {}, { live: true });
|
|
283
|
+
|
|
284
|
+
const unsubscribe = todos.subscribe((snapshot) => {
|
|
285
|
+
render(snapshot.data, snapshot.status);
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
`watch()` reads synchronously, fetches only missing or stale projections,
|
|
290
|
+
deduplicates work, and optionally maintains the generated live operation.
|
|
291
|
+
`read()` is side-effect-free. `dehydrate()` and `hydrate()` transfer confirmed
|
|
292
|
+
request-local state without exposing a public storage schema.
|
|
293
|
+
|
|
294
|
+
The replica stores normalized records and exact argument-sensitive indexes,
|
|
295
|
+
not GraphQL response blobs. Generated selection metadata reconstructs each
|
|
296
|
+
operation result from that shared state, so a detail read, list read, live
|
|
297
|
+
frame, or optimistic command can update every affected view in one transaction.
|
|
298
|
+
|
|
299
|
+
## Commands and optimistic UI
|
|
300
|
+
|
|
301
|
+
Generated `createCommands` binds the service-owned command artifacts to the
|
|
302
|
+
same replica and GraphQL transport. A command call:
|
|
303
|
+
|
|
304
|
+
1. validates and freezes its typed input;
|
|
305
|
+
2. fills generated UUIDv7, ULID, or literal defaults exactly once;
|
|
306
|
+
3. applies the generated optimistic effect transaction;
|
|
307
|
+
4. dispatches the exact compiler-owned mutation;
|
|
308
|
+
5. keeps ambiguous commits recoverable by command ID;
|
|
309
|
+
6. confirms or rejects only its own optimistic layer;
|
|
310
|
+
7. resolves projected completion only after exact causal evidence arrives.
|
|
311
|
+
|
|
312
|
+
Applications do not provide list targets, merge functions, mutation update
|
|
313
|
+
callbacks, or invalidation maps. If the compiler cannot prove safe maintenance,
|
|
314
|
+
the generated plan marks the affected projection stale and the replica performs
|
|
315
|
+
one deduplicated revalidation.
|
|
316
|
+
|
|
317
|
+
Callers may bound their own causal wait without inventing a rollback:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
const receipt = await commands.todo.create(
|
|
321
|
+
{ title: 'Ship it' },
|
|
322
|
+
{ signal: AbortSignal.timeout(5_000) }
|
|
323
|
+
);
|
|
324
|
+
await receipt.projected;
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Before acceptance the signal cancels dispatch. After finite acceptance it
|
|
328
|
+
rejects only that caller's `receipt.projected` wait; the optimistic layer and
|
|
329
|
+
internal causal tracking remain active, and `receipt.status()` stays available.
|
|
330
|
+
|
|
331
|
+
## React
|
|
332
|
+
|
|
333
|
+
Install React and use the optional adapter over an application-owned replica:
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
import {
|
|
337
|
+
DistributedProvider,
|
|
338
|
+
useDistributedQuery
|
|
339
|
+
} from '@hops-ops/distributed/react';
|
|
340
|
+
import { Operation_Todos } from './generated/distributed/index.js';
|
|
341
|
+
|
|
342
|
+
function TodosView() {
|
|
343
|
+
const todos = useDistributedQuery(Operation_Todos, {}, { live: true });
|
|
344
|
+
return todos.complete
|
|
345
|
+
? todos.data.todos.map((todo) => <div key={todo.todo_id}>{todo.title}</div>)
|
|
346
|
+
: null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
root.render(
|
|
350
|
+
<DistributedProvider replica={replica}>
|
|
351
|
+
<TodosView />
|
|
352
|
+
</DistributedProvider>
|
|
353
|
+
);
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
The adapter is only a `useSyncExternalStore` bridge. It does not add another
|
|
357
|
+
cache, transport, auth lifecycle, or command path. For SSR, create one replica
|
|
358
|
+
per request and hydrate only under the same authoritative scope.
|
|
359
|
+
|
|
360
|
+
## Persistence and diagnostics
|
|
361
|
+
|
|
362
|
+
The default replica is memory-only. Optional IndexedDB persistence is explicit,
|
|
363
|
+
confirmed-state-only, and governed by generated/application model policy.
|
|
364
|
+
Optimistic layers, command inputs, credentials, cache authority, and live
|
|
365
|
+
connections are never persisted as replica data.
|
|
366
|
+
|
|
367
|
+
Diagnostics are also opt-in:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
import {
|
|
371
|
+
createReplicaDiagnostics
|
|
372
|
+
} from '@hops-ops/distributed/diagnostics';
|
|
373
|
+
|
|
374
|
+
const diagnostics = createReplicaDiagnostics();
|
|
375
|
+
const replica = createDistributedReplica({ transport, diagnostics });
|
|
376
|
+
const commands = createCommands(replica, transport, { diagnostics });
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Snapshots explain operation artifacts, normalized records, index coverage,
|
|
380
|
+
optimistic layers, causal receipts, revalidation, response fences, and garbage
|
|
381
|
+
collection. Defaults pseudonymize identities and omit values, arguments,
|
|
382
|
+
credentials, trusted presets, raw command inputs, and cache scope. Revealing
|
|
383
|
+
additional development detail requires an in-process capability plus an
|
|
384
|
+
explicit redactor.
|
|
385
|
+
|
|
386
|
+
## Protocol and security boundaries
|
|
387
|
+
|
|
388
|
+
Every accepted artifact and response is protocol v1 and carries an exact
|
|
389
|
+
schema/client-surface binding. Every response is also bound to a server-issued
|
|
390
|
+
cache scope, operation ID, and trusted-preset inventory; any supplied record
|
|
391
|
+
clocks or index vector are bound to that same scope. Missing, malformed,
|
|
392
|
+
stale-schema, or cross-surface evidence fails closed. An exact authorized
|
|
393
|
+
payload without a safely comparable index vector may render, but it cannot
|
|
394
|
+
advance index clocks/vectors, observations, live resume, or optimistic
|
|
395
|
+
confirmation. Independently valid record clocks remain usable.
|
|
396
|
+
|
|
397
|
+
OIDC credentials authorize transport requests; decoded client claims never
|
|
398
|
+
create cache authority. GraphQL remains the API and command proxy layer, while
|
|
399
|
+
the service's SQL read models remain authoritative.
|
|
400
|
+
|
|
401
|
+
Normative architecture and API decisions live in the Distributed GitKB,
|
|
402
|
+
including `specs/query-layer/v1/cache-engine`. They are intentionally not
|
|
403
|
+
duplicated as decision documents in this package.
|
|
404
|
+
|
|
405
|
+
## Public entry points
|
|
406
|
+
|
|
407
|
+
- `@hops-ops/distributed` — GraphQL HTTP/WebSocket, auth, and protocol
|
|
408
|
+
primitives.
|
|
409
|
+
- `@hops-ops/distributed/replica` — replica, GraphQL transport, generated
|
|
410
|
+
command runtime, query-plan helpers, and optional persistence.
|
|
411
|
+
- `@hops-ops/distributed/diagnostics` — redacted support snapshots and artifact
|
|
412
|
+
inspection.
|
|
413
|
+
- `@hops-ops/distributed/sveltekit` — Svelte stores, SSR route loading,
|
|
414
|
+
hydration, auth lifecycle, and tree-local generated bindings.
|
|
415
|
+
- `@hops-ops/distributed/sveltekit/vite` — Node-only one-shot/check/watch
|
|
416
|
+
generation, virtual module aliases, and GraphQL HTTP/WebSocket proxy helpers.
|
|
417
|
+
- `@hops-ops/distributed/react` — provider and query hook over the same replica.
|
|
418
|
+
|
|
419
|
+
All other subpaths are private and blocked by the package export map.
|
|
420
|
+
|
|
421
|
+
## Pre-release clean break
|
|
422
|
+
|
|
423
|
+
The earlier pilot API and persistence format are intentionally unsupported.
|
|
424
|
+
There is no `QueryCache`, `CacheTarget`, `ListMergeSpec`, `target/at/by`
|
|
425
|
+
addressing, manual cache-policy map, resource wrapper, document store, legacy
|
|
426
|
+
command pipeline, or package-owned codegen executable.
|
|
427
|
+
|
|
428
|
+
To move an existing pilot application:
|
|
429
|
+
|
|
430
|
+
1. rerun `dctl client` and import its operation/command artifacts;
|
|
431
|
+
2. compose one replica through the framework adapter or core transport;
|
|
432
|
+
3. remove handwritten cache targets, merge/update callbacks, and invalidation
|
|
433
|
+
policies;
|
|
434
|
+
4. discard prior browser cache and SSR payloads rather than migrating them.
|
|
435
|
+
|
|
436
|
+
Only protocol-v1 generated artifacts and server envelopes are accepted.
|
|
437
|
+
|
|
438
|
+
## Verification and release
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
npm ci
|
|
442
|
+
npm run quality
|
|
443
|
+
npm run release:dry-run
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
`quality` typechecks generated consumers, runs behavior and adapter suites,
|
|
447
|
+
packs and installs the real tarball into clean consumers, verifies bundle
|
|
448
|
+
boundaries, and runs `publint`. `release:dry-run` exercises npm's publish
|
|
449
|
+
payload without publishing.
|
|
450
|
+
|
|
451
|
+
Version tags (`vX.Y.Z`) publish with npm provenance through GitHub Actions
|
|
452
|
+
trusted publishing. The package currently uses `UNLICENSED` because the
|
|
453
|
+
repository has no top-level license file; changing that is an explicit
|
|
454
|
+
maintainer decision.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Bearer and DevHeaders mapping shared by HTTP and WebSocket transports. */
|
|
2
|
+
import type { GqlAuth } from './types.js';
|
|
3
|
+
/** HTTP headers for a JSON GraphQL request. */
|
|
4
|
+
export declare function buildAuthHeaders(auth?: GqlAuth): Record<string, string>;
|
|
5
|
+
/** `graphql-transport-ws` connection-init payload. */
|
|
6
|
+
export declare function wsConnectionInitPayload(auth?: GqlAuth): Record<string, string>;
|
|
7
|
+
/** Add DevHeaders to a WebSocket URL when bearer authentication is absent. */
|
|
8
|
+
export declare function applyWsDevHeaderParams(url: URL, auth?: GqlAuth): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** HTTP headers for a JSON GraphQL request. */
|
|
2
|
+
export function buildAuthHeaders(auth = {}) {
|
|
3
|
+
const headers = { 'content-type': 'application/json' };
|
|
4
|
+
const token = auth.accessToken?.trim() ?? '';
|
|
5
|
+
if (token) {
|
|
6
|
+
headers.authorization = `Bearer ${token}`;
|
|
7
|
+
}
|
|
8
|
+
else if (auth.userId) {
|
|
9
|
+
headers['x-user-id'] = auth.userId;
|
|
10
|
+
headers['x-role'] = auth.role ?? 'user';
|
|
11
|
+
}
|
|
12
|
+
return headers;
|
|
13
|
+
}
|
|
14
|
+
/** `graphql-transport-ws` connection-init payload. */
|
|
15
|
+
export function wsConnectionInitPayload(auth = {}) {
|
|
16
|
+
const payload = {};
|
|
17
|
+
const token = auth.accessToken?.trim() ?? '';
|
|
18
|
+
if (token) {
|
|
19
|
+
payload.authorization = `Bearer ${token}`;
|
|
20
|
+
payload.accessToken = token;
|
|
21
|
+
}
|
|
22
|
+
else if (auth.userId) {
|
|
23
|
+
payload['x-user-id'] = auth.userId;
|
|
24
|
+
payload['x-role'] = auth.role ?? 'user';
|
|
25
|
+
}
|
|
26
|
+
return payload;
|
|
27
|
+
}
|
|
28
|
+
/** Add DevHeaders to a WebSocket URL when bearer authentication is absent. */
|
|
29
|
+
export function applyWsDevHeaderParams(url, auth = {}) {
|
|
30
|
+
const token = auth.accessToken?.trim() ?? '';
|
|
31
|
+
if (token || !auth.userId)
|
|
32
|
+
return;
|
|
33
|
+
url.searchParams.set('x-user-id', auth.userId);
|
|
34
|
+
url.searchParams.set('x-role', auth.role ?? 'user');
|
|
35
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { createReplicaDevelopmentCapability, createReplicaDiagnostics, inspectReplicaCommandArtifact, inspectReplicaOperationArtifact } from './replica/diagnostics.js';
|
|
2
|
+
export type { ReplicaArtifactSourceLocation, ReplicaCommandArtifactInspection, ReplicaCommandEffectInspection, ReplicaDevelopmentCapability, ReplicaDiagnosticEvent, ReplicaDiagnosticEventInput, ReplicaDiagnosticFieldValueContext, ReplicaDiagnosticFieldValuePolicy, ReplicaDiagnosticIndex, ReplicaDiagnosticIndexInput, ReplicaDiagnosticLayer, ReplicaDiagnosticLayerInput, ReplicaDiagnosticReceipt, ReplicaDiagnosticReceiptExpectationInput, ReplicaDiagnosticReceiptInput, ReplicaDiagnosticRecord, ReplicaDiagnosticRecordInput, ReplicaDiagnosticReasonContext, ReplicaDiagnosticReasonPolicy, ReplicaDiagnostics, ReplicaDiagnosticsOptions, ReplicaDiagnosticsSink, ReplicaDiagnosticsSnapshot, ReplicaDiagnosticScopeInput, ReplicaDiagnosticStateInput, ReplicaOperationArtifactInspection, ReplicaOperationIndexInspection, ReplicaOperationInjectedFieldInspection } from './replica/diagnostics.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createReplicaDevelopmentCapability, createReplicaDiagnostics, inspectReplicaCommandArtifact, inspectReplicaOperationArtifact } from './replica/diagnostics.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Normalize GraphQL documents for HTTP and WebSocket wire formats. */
|
|
2
|
+
import { type DocumentNode } from 'graphql';
|
|
3
|
+
import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
4
|
+
import type { GraphqlVariables } from './types.js';
|
|
5
|
+
/** A source string, GraphQL AST, or code-generated typed document. */
|
|
6
|
+
export type GqlDocument<TData = unknown, TVariables extends GraphqlVariables = GraphqlVariables> = string | DocumentNode | TypedDocumentNode<TData, TVariables>;
|
|
7
|
+
/** Convert a string or AST document to the GraphQL source sent on the wire. */
|
|
8
|
+
export declare function documentToString(document: GqlDocument): string;
|
package/dist/document.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Normalize GraphQL documents for HTTP and WebSocket wire formats. */
|
|
2
|
+
import { print } from 'graphql';
|
|
3
|
+
/** Convert a string or AST document to the GraphQL source sent on the wire. */
|
|
4
|
+
export function documentToString(document) {
|
|
5
|
+
return typeof document === 'string' ? document : print(document);
|
|
6
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Authentication identity helpers for display and exact credential fencing. */
|
|
2
|
+
import type { GqlAuth } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Produce a stable UX identity without embedding a bearer token in labels.
|
|
5
|
+
*
|
|
6
|
+
* This value is not authoritative and must never decide cache reuse. JWTs use
|
|
7
|
+
* the unverified `sub` claim only so UI state can retain a friendly identity
|
|
8
|
+
* label across refreshes.
|
|
9
|
+
* Opaque bearer tokens use a non-cryptographic hash of the complete token.
|
|
10
|
+
* DevHeaders use the user and role pair.
|
|
11
|
+
*/
|
|
12
|
+
export declare function authIdentityKey(auth: GqlAuth): string;
|
|
13
|
+
/**
|
|
14
|
+
* Exact local credential comparison used only to invalidate in-flight client work.
|
|
15
|
+
*
|
|
16
|
+
* It can close a generation, but cannot authorize reuse: only a server-issued
|
|
17
|
+
* Distributed cache scope does that for the replica.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sameAuthCredential(left: GqlAuth, right: GqlAuth): boolean;
|
|
20
|
+
/** Detach caller-owned auth objects before transport and generation fencing. */
|
|
21
|
+
export declare function snapshotAuthCredential(auth: GqlAuth): Readonly<GqlAuth>;
|
|
22
|
+
/** Decode an unverified JWT payload's `sub` claim for UI cache identity only. */
|
|
23
|
+
export declare function jwtPayloadSub(token: string): string | null;
|
package/dist/identity.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Produce a stable UX identity without embedding a bearer token in labels.
|
|
3
|
+
*
|
|
4
|
+
* This value is not authoritative and must never decide cache reuse. JWTs use
|
|
5
|
+
* the unverified `sub` claim only so UI state can retain a friendly identity
|
|
6
|
+
* label across refreshes.
|
|
7
|
+
* Opaque bearer tokens use a non-cryptographic hash of the complete token.
|
|
8
|
+
* DevHeaders use the user and role pair.
|
|
9
|
+
*/
|
|
10
|
+
export function authIdentityKey(auth) {
|
|
11
|
+
const token = auth.accessToken?.trim() ?? '';
|
|
12
|
+
if (token) {
|
|
13
|
+
const subject = jwtPayloadSub(token);
|
|
14
|
+
return subject ? `sub:${subject}` : `bearer:${hashString(token)}`;
|
|
15
|
+
}
|
|
16
|
+
return `dev:${auth.userId ?? ''}:${auth.role ?? ''}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Exact local credential comparison used only to invalidate in-flight client work.
|
|
20
|
+
*
|
|
21
|
+
* It can close a generation, but cannot authorize reuse: only a server-issued
|
|
22
|
+
* Distributed cache scope does that for the replica.
|
|
23
|
+
*/
|
|
24
|
+
export function sameAuthCredential(left, right) {
|
|
25
|
+
const leftToken = left.accessToken?.trim() ?? '';
|
|
26
|
+
const rightToken = right.accessToken?.trim() ?? '';
|
|
27
|
+
if (leftToken || rightToken) {
|
|
28
|
+
return leftToken.length > 0 && leftToken === rightToken;
|
|
29
|
+
}
|
|
30
|
+
return ((left.userId ?? '') === (right.userId ?? '') &&
|
|
31
|
+
(left.role ?? '') === (right.role ?? ''));
|
|
32
|
+
}
|
|
33
|
+
/** Detach caller-owned auth objects before transport and generation fencing. */
|
|
34
|
+
export function snapshotAuthCredential(auth) {
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
...(auth.accessToken === undefined
|
|
37
|
+
? {}
|
|
38
|
+
: { accessToken: auth.accessToken }),
|
|
39
|
+
...(auth.userId === undefined ? {} : { userId: auth.userId }),
|
|
40
|
+
...(auth.role === undefined ? {} : { role: auth.role })
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Decode an unverified JWT payload's `sub` claim for UI cache identity only. */
|
|
44
|
+
export function jwtPayloadSub(token) {
|
|
45
|
+
const parts = token.split('.');
|
|
46
|
+
if (parts.length !== 3 || !parts[1])
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const payload = JSON.parse(base64UrlDecode(parts[1]));
|
|
50
|
+
return typeof payload.sub === 'string' && payload.sub.length > 0 ? payload.sub : null;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function base64UrlDecode(segment) {
|
|
57
|
+
if (typeof globalThis.atob !== 'function') {
|
|
58
|
+
throw new Error('base64 decoding is unavailable in this runtime');
|
|
59
|
+
}
|
|
60
|
+
const padding = segment.length % 4 === 0 ? '' : '='.repeat(4 - (segment.length % 4));
|
|
61
|
+
const binary = globalThis.atob(segment.replace(/-/g, '+').replace(/_/g, '/') + padding);
|
|
62
|
+
const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
63
|
+
return new TextDecoder().decode(bytes);
|
|
64
|
+
}
|
|
65
|
+
/** FNV-1a 32-bit: stable obfuscation for opaque tokens, not a security primitive. */
|
|
66
|
+
function hashString(value) {
|
|
67
|
+
let hash = 0x811c9dc5;
|
|
68
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
69
|
+
hash ^= value.charCodeAt(index);
|
|
70
|
+
hash = Math.imul(hash, 0x01000193);
|
|
71
|
+
}
|
|
72
|
+
return (hash >>> 0).toString(16);
|
|
73
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { GqlAuth, GqlError, GqlErrorLocation, GqlResult, GraphqlVariables } from './types.js';
|
|
2
|
+
export { DISTRIBUTED_PROTOCOL_VERSION, DistributedProtocolError, compareDistributedDecimal, distributedLiveResumeExtensions, parseDistributedProtocolEnvelope, parseGraphqlResponseExtensions, type DistributedDecimalString, type DistributedCommandConsistency, type DistributedCommandMetadata, type DistributedCommandState, type DistributedIndexRevision, type DistributedLiveCursor, type DistributedLiveMetadata, type DistributedLiveResumeExtensions, type DistributedOpaqueString, type DistributedProjectionExpectation, type DistributedProjectionObservation, type DistributedProtocolValue, type DistributedProtocolEnvelope, type DistributedProtocolErrorCode, type DistributedQuerySnapshot, type DistributedRecordRevision, type DistributedTrustedPreset, type DistributedTrustedPresetCodec, type GraphqlResponseExtensions } from './protocol.js';
|
|
3
|
+
export { documentToString, type GqlDocument } from './document.js';
|
|
4
|
+
export { applyWsDevHeaderParams, buildAuthHeaders, wsConnectionInitPayload } from './auth-headers.js';
|
|
5
|
+
export { requestGraphql, type FetchLike, type RequestGraphqlOptions } from './request.js';
|
|
6
|
+
export { graphqlWsUrl, httpUrlToWsUrl, subscribe, type GqlWsHandlers, type GqlWsResult, type SubscribeOptions, type WebSocketConstructor } from './websocket.js';
|
|
7
|
+
export { authIdentityKey, jwtPayloadSub } from './identity.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { DISTRIBUTED_PROTOCOL_VERSION, DistributedProtocolError, compareDistributedDecimal, distributedLiveResumeExtensions, parseDistributedProtocolEnvelope, parseGraphqlResponseExtensions } from './protocol.js';
|
|
2
|
+
export { documentToString } from './document.js';
|
|
3
|
+
export { applyWsDevHeaderParams, buildAuthHeaders, wsConnectionInitPayload } from './auth-headers.js';
|
|
4
|
+
export { requestGraphql } from './request.js';
|
|
5
|
+
export { graphqlWsUrl, httpUrlToWsUrl, subscribe } from './websocket.js';
|
|
6
|
+
export { authIdentityKey, jwtPayloadSub } from './identity.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { cacheIndexKey } from './helpers.js';
|
|
2
|
+
import type { CacheEngine, CacheEngineOptions } from './types.js';
|
|
3
|
+
/** Create the selected private cache-engine implementation. */
|
|
4
|
+
export declare function createCacheEngine(options?: CacheEngineOptions): CacheEngine;
|
|
5
|
+
export { cacheIndexKey };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PurposeBuiltCacheEngine } from './engine.js';
|
|
2
|
+
import { cacheIndexKey } from './helpers.js';
|
|
3
|
+
/** Create the selected private cache-engine implementation. */
|
|
4
|
+
export function createCacheEngine(options = {}) {
|
|
5
|
+
return new PurposeBuiltCacheEngine(options);
|
|
6
|
+
}
|
|
7
|
+
export { cacheIndexKey };
|