@qlzt/dsh-api-remote-access 0.0.0-stage → 0.2.1-rc.2
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/LICENSE +21 -0
- package/README.i18n.yaml +34 -0
- package/README.md +96 -2
- package/README.zh.md +97 -0
- package/lib/client.js +733 -0
- package/lib/index.js +441 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +233 -0
- package/lib/typert.remote-client.d.ts +26 -0
- package/lib/typert.remote-client.js +75 -0
- package/lib/types/client/index.d.ts +22 -0
- package/lib/types/client/index.js +25 -0
- package/lib/types/client/mount.d.ts +50 -0
- package/lib/types/client/mount.js +81 -0
- package/lib/types/client/state.d.ts +63 -0
- package/lib/types/client/state.js +182 -0
- package/lib/types/index.d.ts +121 -0
- package/lib/types/index.js +405 -0
- package/lib/types/snapshot.d.ts +8 -0
- package/lib/types/snapshot.js +8 -0
- package/lib/types/types.d.ts +130 -0
- package/lib/types/types.js +7 -0
- package/package.json +81 -4
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/* Generated by @qlzt/dsh-typert-generator from FaceModel — do not edit. */
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
import { createTypertRemoteInfo } from '@qlzt/dsh-typert-protocol/remote-info'
|
|
5
|
+
|
|
6
|
+
let _deepseek_ai_dsh_api_remote_access_remoteAccess_follow_result$schema$value
|
|
7
|
+
const _deepseek_ai_dsh_api_remote_access_remoteAccess_follow_result$schema = () => (_deepseek_ai_dsh_api_remote_access_remoteAccess_follow_result$schema$value ??= z.union([z.object({
|
|
8
|
+
'type': z.literal("baseline").readonly(),
|
|
9
|
+
'owner': z.literal(true).readonly(),
|
|
10
|
+
}), z.object({
|
|
11
|
+
'type': z.literal("baseline").readonly(),
|
|
12
|
+
'owner': z.literal(false).readonly(),
|
|
13
|
+
'grants': z.array(z.object({
|
|
14
|
+
'resource': z.union([z.object({
|
|
15
|
+
'kind': z.literal("global").readonly(),
|
|
16
|
+
}), z.object({
|
|
17
|
+
'kind': z.literal("session").readonly(),
|
|
18
|
+
'id': z.string().readonly(),
|
|
19
|
+
}), z.object({
|
|
20
|
+
'kind': z.literal("workspace").readonly(),
|
|
21
|
+
'id': z.string().readonly(),
|
|
22
|
+
})]).readonly(),
|
|
23
|
+
'actions': z.array(z.string()).readonly().describe('Endpoint actions allowed on this resource without call arguments, sorted; plain strings because Typert generates no codec for template literal types.'),
|
|
24
|
+
})).readonly(),
|
|
25
|
+
}), z.object({
|
|
26
|
+
'type': z.literal("upsert").readonly(),
|
|
27
|
+
'grant': z.object({
|
|
28
|
+
'resource': z.union([z.object({
|
|
29
|
+
'kind': z.literal("global").readonly(),
|
|
30
|
+
}), z.object({
|
|
31
|
+
'kind': z.literal("session").readonly(),
|
|
32
|
+
'id': z.string().readonly(),
|
|
33
|
+
}), z.object({
|
|
34
|
+
'kind': z.literal("workspace").readonly(),
|
|
35
|
+
'id': z.string().readonly(),
|
|
36
|
+
})]).readonly(),
|
|
37
|
+
'actions': z.array(z.string()).readonly().describe('Endpoint actions allowed on this resource without call arguments, sorted; plain strings because Typert generates no codec for template literal types.'),
|
|
38
|
+
}).readonly(),
|
|
39
|
+
}), z.object({
|
|
40
|
+
'type': z.literal("remove").readonly(),
|
|
41
|
+
'resource': z.union([z.object({
|
|
42
|
+
'kind': z.literal("global").readonly(),
|
|
43
|
+
}), z.object({
|
|
44
|
+
'kind': z.literal("session").readonly(),
|
|
45
|
+
'id': z.string().readonly(),
|
|
46
|
+
}), z.object({
|
|
47
|
+
'kind': z.literal("workspace").readonly(),
|
|
48
|
+
'id': z.string().readonly(),
|
|
49
|
+
})]).readonly(),
|
|
50
|
+
})]))
|
|
51
|
+
|
|
52
|
+
const $remote = createTypertRemoteInfo({
|
|
53
|
+
package: '@qlzt/dsh-api-remote-access',
|
|
54
|
+
methods: {
|
|
55
|
+
'remoteAccess/follow': {
|
|
56
|
+
service: 'remoteAccess',
|
|
57
|
+
description: 'Stream the calling Peer\'s access snapshot: one baseline, then `upsert` and `remove` frames after each `invalidate()` and each policy registration or removal that changes a grant. The owner receives only `{ owner: true }`. The generation ends when `signal` aborts or this service unloads, so a reloaded service starts every reader from a fresh baseline.',
|
|
58
|
+
mode: 'stream',
|
|
59
|
+
invocation: { kind: 'direct' },
|
|
60
|
+
parameters: [
|
|
61
|
+
],
|
|
62
|
+
cancellation: { parameter: 'signal' },
|
|
63
|
+
result: {
|
|
64
|
+
mode: 'strict',
|
|
65
|
+
typeSymbol: '@qlzt/dsh-api-remote-access/types#RemoteAccessFrame',
|
|
66
|
+
create: _deepseek_ai_dsh_api_remote_access_remoteAccess_follow_result$schema,
|
|
67
|
+
description: 'the access frames of one generation.',
|
|
68
|
+
jsonSchema: {"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"baseline"},"owner":{"type":"boolean","const":true}},"required":["type","owner"],"additionalProperties":true},{"type":"object","properties":{"type":{"type":"string","const":"baseline"},"owner":{"type":"boolean","const":false},"grants":{"type":"array","items":{"type":"object","properties":{"resource":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"global"}},"required":["kind"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","const":"session"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","const":"workspace"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":true}]},"actions":{"type":"array","items":{"type":"string"},"description":"Endpoint actions allowed on this resource without call arguments, sorted; plain strings because Typert generates no codec for template literal types."}},"required":["resource","actions"],"additionalProperties":true}}},"required":["type","owner","grants"],"additionalProperties":true},{"type":"object","properties":{"type":{"type":"string","const":"upsert"},"grant":{"type":"object","properties":{"resource":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"global"}},"required":["kind"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","const":"session"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","const":"workspace"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":true}]},"actions":{"type":"array","items":{"type":"string"},"description":"Endpoint actions allowed on this resource without call arguments, sorted; plain strings because Typert generates no codec for template literal types."}},"required":["resource","actions"],"additionalProperties":true}},"required":["type","grant"],"additionalProperties":true},{"type":"object","properties":{"type":{"type":"string","const":"remove"},"resource":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"global"}},"required":["kind"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","const":"session"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","const":"workspace"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":true}]}},"required":["type","resource"],"additionalProperties":true}]},
|
|
69
|
+
},
|
|
70
|
+
sourceLocation: {"file":"packages/api/remote-access/src/index.ts","line":271,"column":10},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
}, z)
|
|
74
|
+
|
|
75
|
+
export const TYPERT = {
|
|
76
|
+
package: '@qlzt/dsh-api-remote-access',
|
|
77
|
+
face: 'host',
|
|
78
|
+
schemas: [
|
|
79
|
+
],
|
|
80
|
+
get invocations() { return $remote.descriptors },
|
|
81
|
+
model: {
|
|
82
|
+
"services": [
|
|
83
|
+
{
|
|
84
|
+
"description": "Registry of access policies, the Gateway's access decisions, list filtering, and the per-Peer access snapshot. Every composition that mounts the Gateway mounts this service too.",
|
|
85
|
+
"summary": "Registry of access policies, the Gateway's access decisions, list filtering, and the per-Peer access snapshot.",
|
|
86
|
+
"tags": [],
|
|
87
|
+
"jsDoc": "/**\n * Registry of access policies, the Gateway's access decisions, list\n * filtering, and the per-Peer access snapshot. Every composition that mounts\n * the Gateway mounts this service too.\n */",
|
|
88
|
+
"key": "remoteAccess",
|
|
89
|
+
"exportName": "RemoteAccessService",
|
|
90
|
+
"members": [
|
|
91
|
+
{
|
|
92
|
+
"kind": "method",
|
|
93
|
+
"name": "registerPolicy",
|
|
94
|
+
"signature": "registerPolicy<K extends Exclude<keyof PrincipalMap, 'owner'>>(kind: K, policy: RemoteAccessPolicy<PrincipalMap[K]>): () => void",
|
|
95
|
+
"summary": "Register the policy of one non-owner principal kind in the caller's fiber; open snapshots are recomputed on registration and removal.",
|
|
96
|
+
"jsDoc": "/**\n * Register the policy of one non-owner principal kind in the caller's\n * fiber; open snapshots are recomputed on registration and removal.\n * @param kind - the principal kind the policy serves; never `owner`.\n * @param policy - business-owned bindings and refusal reasons for principals of `kind`.\n * @returns disposer removing this policy.\n * @throws Error when `kind` already has a policy.\n */"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"kind": "method",
|
|
100
|
+
"name": "invalidate",
|
|
101
|
+
"signature": "invalidate(principal?: Principal): void",
|
|
102
|
+
"summary": "Report that the bindings or condition answers for `principal` may have changed.",
|
|
103
|
+
"jsDoc": "/**\n * Report that the bindings or condition answers for `principal` may have\n * changed. Business packages call this on every grant, revocation, mode\n * change, and expiry; open snapshots re-push, and the next failure of each\n * policy is logged again.\n * @param principal - affected principal; omitted for every principal.\n */"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"kind": "method",
|
|
107
|
+
"name": "decide",
|
|
108
|
+
"signature": "decide(request: RemoteAccessRequest<Principal>): RemoteAccessDecision",
|
|
109
|
+
"summary": "Gateway-only: decide one request whose resources the Gateway resolved.",
|
|
110
|
+
"jsDoc": "/**\n * Gateway-only: decide one request whose resources the Gateway resolved.\n * The owner is allowed, and so is `remoteAccess/follow`: every Peer reads\n * its own snapshot. Otherwise the action is allowed only when, on every\n * listed resource, some binding of the principal's policy names that\n * resource, has a role listing the action, and has no condition or a\n * condition that allows. `bindings()` is called once per decision.\n * @param request - the caller or recipient, the action, the resources it\n * touches, and a call's arguments.\n * @returns `allow`; or `deny` with `no-allow` when the principal kind has\n * no policy, the first refusing condition's reason on the first resource\n * no binding allows, else `explain()`'s reason, else `no-allow`;\n * `policy-failed` when `bindings()`, a condition, or `explain()` throws,\n * logged once per policy until the next `invalidate()`. The Gateway\n * throws `remote-access/denied` for a denied call and drops a denied event.\n */"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"kind": "method",
|
|
114
|
+
"name": "allows",
|
|
115
|
+
"signature": "allows(resource: RemoteAccessResource): boolean",
|
|
116
|
+
"summary": "Filter items inside a Remote method.",
|
|
117
|
+
"jsDoc": "/**\n * Filter items inside a Remote method. The invocation is read from the\n * Context that accessed this service, so call it as\n * `this.ctx.remoteAccess.allows(resource)` from the Remote method. Each\n * check reads the policy's bindings again, so a long-running stream sees\n * binding changes from the next check on.\n * @param resource - resource of one result item.\n * @returns true for the owner and outside a Remote call; otherwise whether\n * the invocation's endpoint action is allowed on `resource` without call\n * arguments, false when the policy is absent or fails.\n */"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"kind": "method",
|
|
121
|
+
"name": "snapshotFor",
|
|
122
|
+
"signature": "snapshotFor(principal: Principal): RemoteAccessBaseline",
|
|
123
|
+
"summary": "The baseline a page opens with before its snapshot stream connects.",
|
|
124
|
+
"jsDoc": "/**\n * The baseline a page opens with before its snapshot stream connects. A\n * business package rendering a non-owner page assigns this value to\n * {@link REMOTE_ACCESS_SNAPSHOT_KEY} after every index injection row,\n * replacing the owner baseline the webServer index carries.\n * @param principal - the page's caller identity.\n * @returns the owner baseline for the owner; otherwise the principal's current grants.\n */"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"kind": "method",
|
|
128
|
+
"name": "follow",
|
|
129
|
+
"signature": "@Remote({ mode: 'stream' }) async *follow(signal: AbortSignal): AsyncIterable<RemoteAccessFrame>",
|
|
130
|
+
"summary": "Stream the calling Peer's access snapshot: one baseline, then `upsert` and `remove` frames after each `invalidate()` and each policy registration or removal that changes a grant.",
|
|
131
|
+
"jsDoc": "/**\n * Stream the calling Peer's access snapshot: one baseline, then `upsert`\n * and `remove` frames after each `invalidate()` and each policy\n * registration or removal that changes a grant. The owner receives only\n * `{ owner: true }`. The generation ends when `signal` aborts or this\n * service unloads, so a reloaded service starts every reader from a fresh\n * baseline.\n * @param signal - generation cancellation.\n * @returns the access frames of one generation.\n */"
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
"types": [
|
|
135
|
+
{
|
|
136
|
+
"name": "Branded",
|
|
137
|
+
"declaration": "export type Branded<B extends string> = string & { readonly [BRAND]: B; };"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "Principal",
|
|
141
|
+
"declaration": "export type Principal = PrincipalMap[keyof PrincipalMap];"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"name": "PrincipalMap",
|
|
145
|
+
"declaration": "export interface PrincipalMap {\n owner: { readonly kind: 'owner'; };\n}"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"name": "RemoteAccessAction",
|
|
149
|
+
"declaration": "export type RemoteAccessAction = RemoteAccessEndpoint | `event:${string}`;"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"name": "RemoteAccessBaseline",
|
|
153
|
+
"declaration": "export type RemoteAccessBaseline = Extract<RemoteAccessFrame, { type: 'baseline'; }>;"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"name": "RemoteAccessBinding",
|
|
157
|
+
"declaration": "export type RemoteAccessBinding = { [K in RemoteAccessResourceKind]: { readonly role: RemoteAccessRole<K>; readonly resource: RemoteAccessResourceOf<K>; readonly condition?: RemoteAccessCondition<K>; } }[RemoteAccessResourceKind];"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"name": "RemoteAccessCondition",
|
|
161
|
+
"declaration": "export type RemoteAccessCondition<K extends RemoteAccessResourceKind> = (request: { readonly action: RemoteAccessAction; readonly resource: RemoteAccessResourceOf<K>; readonly args?: Readonly<Record<string, unknown>>; }) => RemoteAccessDecision;"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "RemoteAccessDecision",
|
|
165
|
+
"declaration": "export type RemoteAccessDecision = { readonly kind: 'allow'; } | { readonly kind: 'deny'; readonly reason: string; };"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "RemoteAccessEndpoint",
|
|
169
|
+
"declaration": "export type RemoteAccessEndpoint = `${string}/${string}`;"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"name": "RemoteAccessFrame",
|
|
173
|
+
"declaration": "export type RemoteAccessFrame = { readonly type: 'baseline'; readonly owner: true; } | { readonly type: 'baseline'; readonly owner: false; readonly grants: readonly RemoteAccessGrant[]; } | { readonly type: 'upsert'; readonly grant: RemoteAccessGrant; } | { readonly type: 'remove'; readonly resource: RemoteAccessResource; };"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"name": "RemoteAccessGrant",
|
|
177
|
+
"declaration": "export interface RemoteAccessGrant {\n readonly resource: RemoteAccessResource;\n readonly actions: readonly string[];\n}"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"name": "RemoteAccessPolicy",
|
|
181
|
+
"declaration": "export interface RemoteAccessPolicy<P extends Principal> {\n bindings(principal: P): readonly RemoteAccessBinding[];\n explain?(request: RemoteAccessRequest<P>): string | undefined;\n}"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"name": "RemoteAccessRequest",
|
|
185
|
+
"declaration": "export interface RemoteAccessRequest<P extends Principal> {\n readonly principal: P;\n readonly action: RemoteAccessAction;\n readonly resources: readonly RemoteAccessResource[];\n readonly args?: Readonly<Record<string, unknown>>;\n}"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"name": "RemoteAccessResource",
|
|
189
|
+
"declaration": "export type RemoteAccessResource = { readonly kind: 'global'; } | { [Kind in TypertResourceKind]: { readonly kind: Kind; readonly id: TypertResourceId<TypertResourceMap[Kind]>; } }[TypertResourceKind];"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"name": "RemoteAccessResourceKind",
|
|
193
|
+
"declaration": "export type RemoteAccessResourceKind = 'global' | TypertResourceKind;"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"name": "RemoteAccessResourceOf",
|
|
197
|
+
"declaration": "export type RemoteAccessResourceOf<K extends RemoteAccessResourceKind> = Extract<RemoteAccessResource, { readonly kind: K; }>;"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "RemoteAccessRole",
|
|
201
|
+
"declaration": "export interface RemoteAccessRole<K extends RemoteAccessResourceKind> {\n readonly name: string;\n readonly resource: K;\n readonly actions: readonly RemoteAccessAction[];\n}"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"name": "SessionId",
|
|
205
|
+
"declaration": "export type SessionId = Branded<'SessionId'>;"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"name": "TypertResource",
|
|
209
|
+
"declaration": "export interface TypertResource<Id> {\n readonly [RESOURCE_ID]: Id;\n}"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"name": "TypertResourceId",
|
|
213
|
+
"declaration": "export type TypertResourceId<Resource> = Resource extends TypertResource<infer Id> ? Id : never;"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"name": "TypertResourceKind",
|
|
217
|
+
"declaration": "export type TypertResourceKind = Extract<keyof TypertResourceMap, string>;"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "TypertResourceMap",
|
|
221
|
+
"declaration": "export interface TypertResourceMap {\n session: TypertResource<SessionId>;\n workspace: TypertResource<WorkspaceId>;\n}"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"name": "WorkspaceId",
|
|
225
|
+
"declaration": "export type WorkspaceId = Branded<'WorkspaceId'>;"
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
],
|
|
230
|
+
"events": [],
|
|
231
|
+
"objects": []
|
|
232
|
+
},
|
|
233
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* Generated by @qlzt/dsh-typert-generator from the Host FaceModel — do not edit. */
|
|
2
|
+
import type {
|
|
3
|
+
RemoteResult,
|
|
4
|
+
RemoteStreamHandle,
|
|
5
|
+
TypertRemoteInfo,
|
|
6
|
+
TypertRemoteMethod,
|
|
7
|
+
} from '@qlzt/dsh-typert-protocol'
|
|
8
|
+
import type { RemoteAccessFrame } from '@qlzt/dsh-api-remote-access/types'
|
|
9
|
+
|
|
10
|
+
declare module '@qlzt/dsh-typert-protocol' {
|
|
11
|
+
interface TypertRemoteNamespace$72656d6f7465416363657373 {
|
|
12
|
+
follow: (signal?: AbortSignal) => RemoteStreamHandle<RemoteAccessFrame, never>
|
|
13
|
+
}
|
|
14
|
+
interface TypertRemoteMap {
|
|
15
|
+
'remoteAccess/follow': (signal?: AbortSignal) => RemoteStreamHandle<RemoteAccessFrame, never>
|
|
16
|
+
}
|
|
17
|
+
interface TypertRemoteNamespaceMap {
|
|
18
|
+
'remoteAccess': TypertRemoteNamespace$72656d6f7465416363657373
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export declare const TYPERT_REMOTE: TypertRemoteInfo<{
|
|
23
|
+
readonly 'remoteAccess/follow': TypertRemoteMethod<{ }, RemoteAccessFrame, 'stream'>
|
|
24
|
+
}>
|
|
25
|
+
export default TYPERT_REMOTE
|
|
26
|
+
//# sourceMappingURL=typert.remote-client.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* Generated by @qlzt/dsh-typert-generator from the Host FaceModel — do not edit. */
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
import { createTypertRemoteInfo } from '@qlzt/dsh-typert-protocol/remote-info'
|
|
5
|
+
|
|
6
|
+
let _deepseek_ai_dsh_api_remote_access_remoteAccess_follow_result$schema$value
|
|
7
|
+
const _deepseek_ai_dsh_api_remote_access_remoteAccess_follow_result$schema = () => (_deepseek_ai_dsh_api_remote_access_remoteAccess_follow_result$schema$value ??= z.union([z.object({
|
|
8
|
+
'type': z.literal("baseline").readonly(),
|
|
9
|
+
'owner': z.literal(true).readonly(),
|
|
10
|
+
}), z.object({
|
|
11
|
+
'type': z.literal("baseline").readonly(),
|
|
12
|
+
'owner': z.literal(false).readonly(),
|
|
13
|
+
'grants': z.array(z.object({
|
|
14
|
+
'resource': z.union([z.object({
|
|
15
|
+
'kind': z.literal("global").readonly(),
|
|
16
|
+
}), z.object({
|
|
17
|
+
'kind': z.literal("session").readonly(),
|
|
18
|
+
'id': z.string().readonly(),
|
|
19
|
+
}), z.object({
|
|
20
|
+
'kind': z.literal("workspace").readonly(),
|
|
21
|
+
'id': z.string().readonly(),
|
|
22
|
+
})]).readonly(),
|
|
23
|
+
'actions': z.array(z.string()).readonly().describe('Endpoint actions allowed on this resource without call arguments, sorted; plain strings because Typert generates no codec for template literal types.'),
|
|
24
|
+
})).readonly(),
|
|
25
|
+
}), z.object({
|
|
26
|
+
'type': z.literal("upsert").readonly(),
|
|
27
|
+
'grant': z.object({
|
|
28
|
+
'resource': z.union([z.object({
|
|
29
|
+
'kind': z.literal("global").readonly(),
|
|
30
|
+
}), z.object({
|
|
31
|
+
'kind': z.literal("session").readonly(),
|
|
32
|
+
'id': z.string().readonly(),
|
|
33
|
+
}), z.object({
|
|
34
|
+
'kind': z.literal("workspace").readonly(),
|
|
35
|
+
'id': z.string().readonly(),
|
|
36
|
+
})]).readonly(),
|
|
37
|
+
'actions': z.array(z.string()).readonly().describe('Endpoint actions allowed on this resource without call arguments, sorted; plain strings because Typert generates no codec for template literal types.'),
|
|
38
|
+
}).readonly(),
|
|
39
|
+
}), z.object({
|
|
40
|
+
'type': z.literal("remove").readonly(),
|
|
41
|
+
'resource': z.union([z.object({
|
|
42
|
+
'kind': z.literal("global").readonly(),
|
|
43
|
+
}), z.object({
|
|
44
|
+
'kind': z.literal("session").readonly(),
|
|
45
|
+
'id': z.string().readonly(),
|
|
46
|
+
}), z.object({
|
|
47
|
+
'kind': z.literal("workspace").readonly(),
|
|
48
|
+
'id': z.string().readonly(),
|
|
49
|
+
})]).readonly(),
|
|
50
|
+
})]))
|
|
51
|
+
|
|
52
|
+
export const TYPERT_REMOTE = createTypertRemoteInfo({
|
|
53
|
+
package: '@qlzt/dsh-api-remote-access',
|
|
54
|
+
methods: {
|
|
55
|
+
'remoteAccess/follow': {
|
|
56
|
+
service: 'remoteAccess',
|
|
57
|
+
description: 'Stream the calling Peer\'s access snapshot: one baseline, then `upsert` and `remove` frames after each `invalidate()` and each policy registration or removal that changes a grant. The owner receives only `{ owner: true }`. The generation ends when `signal` aborts or this service unloads, so a reloaded service starts every reader from a fresh baseline.',
|
|
58
|
+
mode: 'stream',
|
|
59
|
+
invocation: { kind: 'direct' },
|
|
60
|
+
parameters: [
|
|
61
|
+
],
|
|
62
|
+
cancellation: { parameter: 'signal' },
|
|
63
|
+
result: {
|
|
64
|
+
mode: 'strict',
|
|
65
|
+
typeSymbol: '@qlzt/dsh-api-remote-access/types#RemoteAccessFrame',
|
|
66
|
+
create: _deepseek_ai_dsh_api_remote_access_remoteAccess_follow_result$schema,
|
|
67
|
+
description: 'the access frames of one generation.',
|
|
68
|
+
jsonSchema: {"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"baseline"},"owner":{"type":"boolean","const":true}},"required":["type","owner"],"additionalProperties":true},{"type":"object","properties":{"type":{"type":"string","const":"baseline"},"owner":{"type":"boolean","const":false},"grants":{"type":"array","items":{"type":"object","properties":{"resource":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"global"}},"required":["kind"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","const":"session"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","const":"workspace"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":true}]},"actions":{"type":"array","items":{"type":"string"},"description":"Endpoint actions allowed on this resource without call arguments, sorted; plain strings because Typert generates no codec for template literal types."}},"required":["resource","actions"],"additionalProperties":true}}},"required":["type","owner","grants"],"additionalProperties":true},{"type":"object","properties":{"type":{"type":"string","const":"upsert"},"grant":{"type":"object","properties":{"resource":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"global"}},"required":["kind"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","const":"session"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","const":"workspace"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":true}]},"actions":{"type":"array","items":{"type":"string"},"description":"Endpoint actions allowed on this resource without call arguments, sorted; plain strings because Typert generates no codec for template literal types."}},"required":["resource","actions"],"additionalProperties":true}},"required":["type","grant"],"additionalProperties":true},{"type":"object","properties":{"type":{"type":"string","const":"remove"},"resource":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"global"}},"required":["kind"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","const":"session"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":true},{"type":"object","properties":{"kind":{"type":"string","const":"workspace"},"id":{"type":"string"}},"required":["kind","id"],"additionalProperties":true}]}},"required":["type","resource"],"additionalProperties":true}]},
|
|
69
|
+
},
|
|
70
|
+
sourceLocation: {"file":"packages/api/remote-access/src/index.ts","line":271,"column":10},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
}, z)
|
|
74
|
+
|
|
75
|
+
export default TYPERT_REMOTE
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client face: starts from the access snapshot the index injected, follows
|
|
3
|
+
* the calling Peer's snapshot stream, provides the `remoteAccess` Client
|
|
4
|
+
* service whose `source(kind, id, action)` answers for any declared-kind
|
|
5
|
+
* resource, and publishes `useRemoteAccess(action)` on the global resource.
|
|
6
|
+
* Domain UI adapters publish their own resource Hooks over `source()`. Every
|
|
7
|
+
* answer is a boolean: true for the owner, false without a snapshot or after
|
|
8
|
+
* a non-owner stream fails; the Host enforces every call regardless.
|
|
9
|
+
* @module @qlzt/dsh-api-remote-access/client
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
export type { RemoteAccessAction, RemoteAccessEndpoint, RemoteAccessFrame, RemoteAccessGrant, RemoteAccessResource, TypertResourceKind, } from '../types.ts';
|
|
13
|
+
export type { ClientRemoteAccess, RemoteEndpoint } from './mount.ts';
|
|
14
|
+
/** Required Client services: the Remote mount face and the Slot registry. */
|
|
15
|
+
export declare const inject: string[];
|
|
16
|
+
/**
|
|
17
|
+
* Mount the generated `remoteAccess` namespace and publish the access Hooks.
|
|
18
|
+
* @param ctx - Client root Context.
|
|
19
|
+
* @returns disposer that withdraws the namespace after the stream stops.
|
|
20
|
+
*/
|
|
21
|
+
export declare function apply(ctx: Context): Promise<() => Promise<void>>;
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client face: starts from the access snapshot the index injected, follows
|
|
3
|
+
* the calling Peer's snapshot stream, provides the `remoteAccess` Client
|
|
4
|
+
* service whose `source(kind, id, action)` answers for any declared-kind
|
|
5
|
+
* resource, and publishes `useRemoteAccess(action)` on the global resource.
|
|
6
|
+
* Domain UI adapters publish their own resource Hooks over `source()`. Every
|
|
7
|
+
* answer is a boolean: true for the owner, false without a snapshot or after
|
|
8
|
+
* a non-owner stream fails; the Host enforces every call regardless.
|
|
9
|
+
* @module @qlzt/dsh-api-remote-access/client
|
|
10
|
+
*/
|
|
11
|
+
import remoteAccessRemote from '@qlzt/dsh-api-remote-access/remote';
|
|
12
|
+
import { REMOTE_ACCESS_SNAPSHOT_KEY } from "../snapshot.js";
|
|
13
|
+
import { mountAccess } from "./mount.js";
|
|
14
|
+
import { parseInjectedBaseline } from "./state.js";
|
|
15
|
+
/** Required Client services: the Remote mount face and the Slot registry. */
|
|
16
|
+
export const inject = ['remote', 'slots'];
|
|
17
|
+
/**
|
|
18
|
+
* Mount the generated `remoteAccess` namespace and publish the access Hooks.
|
|
19
|
+
* @param ctx - Client root Context.
|
|
20
|
+
* @returns disposer that withdraws the namespace after the stream stops.
|
|
21
|
+
*/
|
|
22
|
+
export function apply(ctx) {
|
|
23
|
+
return mountAccess(ctx, remoteAccessRemote, parseInjectedBaseline(Reflect.get(globalThis, REMOTE_ACCESS_SNAPSHOT_KEY)));
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client access mount: follows the calling Peer's access snapshot, provides
|
|
3
|
+
* the `remoteAccess` Client service, and publishes the global-resource `useRemoteAccess` Hook.
|
|
4
|
+
* The entry module supplies the generated `remoteAccess` contribution, so
|
|
5
|
+
* specs mount a fake one.
|
|
6
|
+
* @module @qlzt/dsh-api-remote-access/src/client/mount
|
|
7
|
+
*/
|
|
8
|
+
import { type Context } from '@deepseek-ai/cordis';
|
|
9
|
+
import type { HostObservable } from '@qlzt/dsh-client-ui-slots';
|
|
10
|
+
import type { TypertRemoteContribution, TypertRemoteMap, TypertResourceKind } from '@qlzt/dsh-typert-protocol';
|
|
11
|
+
import { type Baseline } from './state.ts';
|
|
12
|
+
/** Every endpoint the Client Remote namespaces declare. */
|
|
13
|
+
export type RemoteEndpoint = Extract<keyof TypertRemoteMap, string>;
|
|
14
|
+
/** Client access answers over the calling Peer's snapshot; it never issues a request. */
|
|
15
|
+
export interface ClientRemoteAccess {
|
|
16
|
+
/**
|
|
17
|
+
* Whether the current Peer may call `action` on one resource.
|
|
18
|
+
* True for the owner; false without a baseline and after a non-owner
|
|
19
|
+
* stream fails; otherwise whether the resource's grant lists the action.
|
|
20
|
+
* @param kind - resource kind declared in `TypertResourceMap`.
|
|
21
|
+
* @param id - resource identity.
|
|
22
|
+
* @param action - endpoint action the caller would invoke.
|
|
23
|
+
* @returns identity-stable boolean source for this `(kind, id, action)`.
|
|
24
|
+
*/
|
|
25
|
+
source(kind: TypertResourceKind, id: string, action: string): HostObservable<boolean>;
|
|
26
|
+
}
|
|
27
|
+
declare module '@deepseek-ai/cordis' {
|
|
28
|
+
interface Context {
|
|
29
|
+
/** Client access answers for the calling Peer. */
|
|
30
|
+
remoteAccess: ClientRemoteAccess;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
declare module '@qlzt/dsh-client-ui-slots' {
|
|
34
|
+
interface GlobalStandardProps {
|
|
35
|
+
/** Whether the current Peer may call `action` on the global resource. */
|
|
36
|
+
useRemoteAccess: (action: RemoteEndpoint) => boolean;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Mount the `remoteAccess` namespace, follow the access snapshot, provide the
|
|
41
|
+
* `remoteAccess` Client service, and publish the global-resource `useRemoteAccess` Hook.
|
|
42
|
+
* Answers start from the page's injected baseline, or false without one,
|
|
43
|
+
* and follow the stream's frames from then on.
|
|
44
|
+
* @param ctx - Client root Context injecting `remote` and `slots`.
|
|
45
|
+
* @param contribution - generated `remoteAccess` Remote contribution.
|
|
46
|
+
* @param initial - the page's injected opening baseline, read before the stream connects.
|
|
47
|
+
* @returns disposer that withdraws the namespace after the stream stops.
|
|
48
|
+
*/
|
|
49
|
+
export declare function mountAccess(ctx: Context, contribution: TypertRemoteContribution, initial?: Baseline): Promise<() => Promise<void>>;
|
|
50
|
+
//# sourceMappingURL=mount.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client access mount: follows the calling Peer's access snapshot, provides
|
|
3
|
+
* the `remoteAccess` Client service, and publishes the global-resource `useRemoteAccess` Hook.
|
|
4
|
+
* The entry module supplies the generated `remoteAccess` contribution, so
|
|
5
|
+
* specs mount a fake one.
|
|
6
|
+
* @module @qlzt/dsh-api-remote-access/src/client/mount
|
|
7
|
+
*/
|
|
8
|
+
import { Service } from '@deepseek-ai/cordis';
|
|
9
|
+
import { RemoteSnapshotStream, RemoteStreamCarrierError, } from '@qlzt/dsh-api-gateway/client';
|
|
10
|
+
import { ClientAccessState } from "./state.js";
|
|
11
|
+
/** The `remoteAccess` Client service over one snapshot owner. */
|
|
12
|
+
class ClientRemoteAccessService extends Service {
|
|
13
|
+
state;
|
|
14
|
+
/**
|
|
15
|
+
* @param ctx - Client Context the service is provided on.
|
|
16
|
+
* @param state - snapshot owner answering every source.
|
|
17
|
+
*/
|
|
18
|
+
constructor(ctx, state) {
|
|
19
|
+
super(ctx, 'remoteAccess');
|
|
20
|
+
this.state = state;
|
|
21
|
+
}
|
|
22
|
+
source(kind, id, action) {
|
|
23
|
+
return this.state.resource(kind, id, action);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Mount the `remoteAccess` namespace, follow the access snapshot, provide the
|
|
28
|
+
* `remoteAccess` Client service, and publish the global-resource `useRemoteAccess` Hook.
|
|
29
|
+
* Answers start from the page's injected baseline, or false without one,
|
|
30
|
+
* and follow the stream's frames from then on.
|
|
31
|
+
* @param ctx - Client root Context injecting `remote` and `slots`.
|
|
32
|
+
* @param contribution - generated `remoteAccess` Remote contribution.
|
|
33
|
+
* @param initial - the page's injected opening baseline, read before the stream connects.
|
|
34
|
+
* @returns disposer that withdraws the namespace after the stream stops.
|
|
35
|
+
*/
|
|
36
|
+
export async function mountAccess(ctx, contribution, initial) {
|
|
37
|
+
const state = new ClientAccessState(initial);
|
|
38
|
+
new ClientRemoteAccessService(ctx, state);
|
|
39
|
+
ctx.slots.provideRoot({
|
|
40
|
+
keyedHooks: { remoteAccess: action => state.global(action) },
|
|
41
|
+
});
|
|
42
|
+
const unmount = await ctx.remote.$mount(contribution);
|
|
43
|
+
const follow = ctx.inject(['remote.remoteAccess'], (remoteCtx) => {
|
|
44
|
+
const control = createAccessStream(remoteCtx.remote, state, remoteCtx.logger);
|
|
45
|
+
control.start();
|
|
46
|
+
remoteCtx.effect(() => async () => { await control.dispose(); }, 'remote-access.client.follow');
|
|
47
|
+
});
|
|
48
|
+
return async () => {
|
|
49
|
+
await follow.dispose();
|
|
50
|
+
await unmount();
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create the reconnecting access snapshot stream. A carrier loss keeps the
|
|
55
|
+
* last snapshot while the stream reconnects; a terminal failure keeps the
|
|
56
|
+
* owner's answers and turns a non-owner's answers false.
|
|
57
|
+
*/
|
|
58
|
+
function createAccessStream(remote, state, logger) {
|
|
59
|
+
const stream = remote.$stream({
|
|
60
|
+
name: 'Remote access stream',
|
|
61
|
+
open: signal => remote.remoteAccess.follow(signal),
|
|
62
|
+
ended: accepted => accepted
|
|
63
|
+
? new RemoteStreamCarrierError('Remote access stream ended without a terminal result')
|
|
64
|
+
: new Error('Remote access stream ended before its opening snapshot'),
|
|
65
|
+
});
|
|
66
|
+
return new RemoteSnapshotStream(stream, {
|
|
67
|
+
name: 'Remote access stream',
|
|
68
|
+
isSnapshot: (frame) => frame.type === 'baseline',
|
|
69
|
+
replace: (frame) => { state.replace(frame); },
|
|
70
|
+
update: (frame) => { state.update(frame); },
|
|
71
|
+
failed: (error) => {
|
|
72
|
+
// Only a non-owner's answers turn false after a terminal failure, so only its report names the recovery.
|
|
73
|
+
if (state.owner)
|
|
74
|
+
logger.error('access snapshot stream failed; owner access answers are unaffected', error);
|
|
75
|
+
else
|
|
76
|
+
logger.error('access snapshot stream failed; reload the page to reconnect', error);
|
|
77
|
+
state.unavailable();
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=mount.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React-free Client access state: the latest `remoteAccess/follow` snapshot
|
|
3
|
+
* and identity-stable boolean sources per resource and action.
|
|
4
|
+
* @module @qlzt/dsh-api-remote-access/src/client/state
|
|
5
|
+
*/
|
|
6
|
+
import type { HostObservable } from '@qlzt/dsh-client-ui-slots';
|
|
7
|
+
import type { RemoteAccessFrame } from '../types.ts';
|
|
8
|
+
/** The opening frame of a snapshot generation. */
|
|
9
|
+
export type Baseline = Extract<RemoteAccessFrame, {
|
|
10
|
+
type: 'baseline';
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Validate the opening baseline an index injection row assigned to the page.
|
|
14
|
+
* @param value - the injected global; `undefined` when the page carries none.
|
|
15
|
+
* @returns the baseline, or `undefined` when none was injected.
|
|
16
|
+
* @throws TypeError when the injected value is not a baseline frame.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseInjectedBaseline(value: unknown): Baseline | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Snapshot owner for one Client generation. Sources are cached per
|
|
21
|
+
* `(kind, id, action)` so Hook bindings keep their identity; each source
|
|
22
|
+
* computes its boolean from the current snapshot.
|
|
23
|
+
*/
|
|
24
|
+
export declare class ClientAccessState {
|
|
25
|
+
private snapshot;
|
|
26
|
+
/** @param initial - the page's injected opening baseline; without one every answer is false until a baseline arrives. */
|
|
27
|
+
constructor(initial?: Baseline);
|
|
28
|
+
private readonly listeners;
|
|
29
|
+
private readonly sources;
|
|
30
|
+
/**
|
|
31
|
+
* Answer for one action on the global resource.
|
|
32
|
+
* @param action - endpoint action the caller may invoke.
|
|
33
|
+
* @returns identity-stable boolean source.
|
|
34
|
+
*/
|
|
35
|
+
global(action: string): HostObservable<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Answer for one action on one resource of a declared kind.
|
|
38
|
+
* @param kind - declared resource kind.
|
|
39
|
+
* @param id - resource identity.
|
|
40
|
+
* @param action - endpoint action the caller may invoke.
|
|
41
|
+
* @returns identity-stable boolean source.
|
|
42
|
+
*/
|
|
43
|
+
resource(kind: string, id: string, action: string): HostObservable<boolean>;
|
|
44
|
+
/**
|
|
45
|
+
* Replace the snapshot from one generation's opening frame.
|
|
46
|
+
* @param frame - owner or non-owner baseline.
|
|
47
|
+
*/
|
|
48
|
+
replace(frame: Baseline): void;
|
|
49
|
+
/**
|
|
50
|
+
* Apply one increment of the current generation.
|
|
51
|
+
* @param frame - `upsert` or `remove`.
|
|
52
|
+
*/
|
|
53
|
+
update(frame: Exclude<RemoteAccessFrame, {
|
|
54
|
+
type: 'baseline';
|
|
55
|
+
}>): void;
|
|
56
|
+
/** Whether the current snapshot is the owner's, whose answers never depend on the stream. */
|
|
57
|
+
get owner(): boolean;
|
|
58
|
+
/** The stream failed terminally: the owner keeps every answer; a non-owner's answers become false. */
|
|
59
|
+
unavailable(): void;
|
|
60
|
+
private cached;
|
|
61
|
+
private publish;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=state.d.ts.map
|