@kubun/plugin-p2p 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/lib/context/delegation.d.ts +4 -0
  2. package/lib/context/delegation.js +1 -0
  3. package/lib/context/group.js +1 -1
  4. package/lib/context/hub.js +1 -1
  5. package/lib/context/join.js +1 -1
  6. package/lib/context/types.d.ts +25 -2
  7. package/lib/groups/broadcast-service.d.ts +137 -4
  8. package/lib/groups/broadcast-service.js +1 -1
  9. package/lib/groups/broadcast.d.ts +120 -0
  10. package/lib/groups/broadcast.js +1 -1
  11. package/lib/groups/events.d.ts +16 -5
  12. package/lib/groups/events.js +1 -1
  13. package/lib/groups/group-handle-registry.d.ts +8 -0
  14. package/lib/groups/group-handle-registry.js +1 -1
  15. package/lib/groups/group-health-monitor.d.ts +45 -0
  16. package/lib/groups/group-health-monitor.js +1 -0
  17. package/lib/groups/invite-payload.d.ts +16 -0
  18. package/lib/groups/join-utils.d.ts +68 -2
  19. package/lib/groups/join-utils.js +1 -1
  20. package/lib/groups/manager.d.ts +15 -4
  21. package/lib/groups/manager.js +1 -1
  22. package/lib/groups/rejoin-codec.d.ts +14 -0
  23. package/lib/groups/rejoin-codec.js +1 -0
  24. package/lib/groups/store-received-grant.d.ts +47 -0
  25. package/lib/groups/store-received-grant.js +1 -0
  26. package/lib/groups/store-received-revocation.d.ts +46 -0
  27. package/lib/groups/store-received-revocation.js +1 -0
  28. package/lib/groups/wire-frame.d.ts +34 -0
  29. package/lib/groups/wire-frame.js +1 -0
  30. package/lib/hub/circle-catchup-requester.d.ts +58 -0
  31. package/lib/hub/circle-catchup-requester.js +1 -0
  32. package/lib/hub/circle-catchup-responder.d.ts +50 -0
  33. package/lib/hub/circle-catchup-responder.js +1 -0
  34. package/lib/hub/epoch-stale-detector.d.ts +18 -0
  35. package/lib/hub/epoch-stale-detector.js +1 -0
  36. package/lib/hub/errors.d.ts +7 -5
  37. package/lib/hub/errors.js +1 -1
  38. package/lib/hub/forward-remote-broadcast.d.ts +3 -3
  39. package/lib/hub/forward-remote-broadcast.js +1 -1
  40. package/lib/hub/group-channel.d.ts +30 -0
  41. package/lib/hub/group-channel.js +1 -1
  42. package/lib/hub/hub-connection.js +1 -1
  43. package/lib/hub/manager.d.ts +5 -4
  44. package/lib/hub/manager.js +1 -1
  45. package/lib/hub/receive-handler.d.ts +20 -2
  46. package/lib/hub/receive-handler.js +1 -1
  47. package/lib/hub/rejoin-manager.d.ts +78 -0
  48. package/lib/hub/rejoin-manager.js +1 -0
  49. package/lib/hub/rejoin-responder.d.ts +32 -0
  50. package/lib/hub/rejoin-responder.js +1 -0
  51. package/lib/hub/relay-manager.d.ts +23 -1
  52. package/lib/hub/relay-manager.js +1 -1
  53. package/lib/hub/send-handler.d.ts +16 -0
  54. package/lib/hub/send-handler.js +1 -1
  55. package/lib/hub/wiring.d.ts +33 -3
  56. package/lib/hub/wiring.js +1 -1
  57. package/lib/index.d.ts +19 -5
  58. package/lib/index.js +1 -1
  59. package/lib/protocol.d.ts +7 -0
  60. package/lib/protocol.js +1 -1
  61. package/lib/schema.d.ts +2 -2
  62. package/lib/schema.js +79 -3
  63. package/lib/sync/authorize.d.ts +25 -0
  64. package/lib/sync/authorize.js +1 -0
  65. package/lib/sync/broadcast-sender.js +1 -1
  66. package/lib/sync/errors.d.ts +11 -0
  67. package/lib/sync/errors.js +1 -0
  68. package/lib/sync/forwarder.d.ts +1 -0
  69. package/lib/sync/forwarder.js +1 -1
  70. package/lib/sync/handlers.d.ts +3 -18
  71. package/lib/sync/handlers.js +1 -1
  72. package/lib/sync/hub-tunnel-sync-listener.d.ts +2 -0
  73. package/lib/sync/hub-tunnel-sync-listener.js +1 -1
  74. package/lib/sync/merkle-apply.js +1 -1
  75. package/lib/sync/merkle-channel.d.ts +19 -0
  76. package/lib/sync/merkle-channel.js +1 -1
  77. package/lib/sync/peer-registry.d.ts +7 -0
  78. package/lib/sync/peer-registry.js +1 -1
  79. package/lib/sync/sync-client.d.ts +2 -0
  80. package/lib/sync/sync-client.js +1 -1
  81. package/lib/sync/sync-manager.js +1 -1
  82. package/lib/types.d.ts +122 -1
  83. package/lib/utils.d.ts +5 -0
  84. package/lib/utils.js +1 -0
  85. package/package.json +41 -41
  86. package/lib/sync/catalog-match.d.ts +0 -13
  87. package/lib/sync/catalog-match.js +0 -1
package/lib/types.d.ts CHANGED
@@ -115,7 +115,15 @@ export type GroupRequestContext = {
115
115
  filterCriteria: string;
116
116
  }) => Promise<CatalogData>;
117
117
  listCatalogs: (ownerDID?: string) => Promise<Array<CatalogData>>;
118
- invite: (groupID: string, joinRequest: string) => Promise<InviteToGroupData>;
118
+ /**
119
+ * Build an invite payload for a join request.
120
+ *
121
+ * `grants` are stringified write-capability JWTs the caller wants the joiner
122
+ * to hold immediately on join. `revocations` are signed revocation JWTs the
123
+ * caller wants the joiner to apply immediately — only verified ones travel;
124
+ * pending revocations (cap not yet known by the inviter) do not.
125
+ */
126
+ invite: (groupID: string, joinRequest: string, grants?: Array<string>, revocations?: Array<string>) => Promise<InviteToGroupData>;
119
127
  join: (invitePayload: string, joinRequestPayload: string) => Promise<JoinGroupData>;
120
128
  leave: (groupID: string) => Promise<LeaveGroupData>;
121
129
  removeMember: (groupID: string, memberDID: string) => Promise<RemoveGroupMemberData>;
@@ -129,6 +137,53 @@ export type GroupRequestContext = {
129
137
  deleteCatalog: (catalogID: string) => Promise<DeleteCatalogData>;
130
138
  deleteCircle: (circleID: string) => Promise<DeleteCircleData>;
131
139
  removeCircleMember: (circleID: string, memberDID: string) => Promise<RemoveCircleMemberData>;
140
+ /**
141
+ * Mint a `document/write` capability granting `to` write access on resources
142
+ * matching `res`. Token is stored grantor-side, broadcast to the group, and
143
+ * returned for direct delivery.
144
+ *
145
+ * `res` is a URN. Most-specific wins; broader URNs grant strictly more:
146
+ * - `urn:kubun:document:<docID>` — single doc
147
+ * - `urn:kubun:model:<modelID>` — every doc of that model owned by the grantor
148
+ * - `urn:kubun:model:<interfaceID>` — every doc of every model implementing
149
+ * the interface (footgun: a grant on a widely-implemented interface like
150
+ * `Owned` or `Timestamped` becomes a near-wildcard)
151
+ * - `urn:kubun:user:<DID>` — every doc owned by that DID (the grantor)
152
+ * - `*` — every doc the grantor owns, present and future
153
+ *
154
+ * Prefer the narrowest URN that satisfies the use case. Broad grants combine
155
+ * with the long default `exp` (30 days) to expand the blast radius of a
156
+ * leaked or misdirected token.
157
+ */
158
+ grantWriteCapability: (params: {
159
+ to: string;
160
+ res: string;
161
+ groupID: string;
162
+ expiresIn?: number;
163
+ }) => Promise<string>;
164
+ /**
165
+ * Revoke a previously-granted `document/write` capability by its `jti`.
166
+ *
167
+ * Revocation is binary: once a peer learns of the revocation, every
168
+ * mutation that authenticates via this `jti` is denied. The check is
169
+ * reactive, not retroactive — mutations already applied locally stay
170
+ * applied; only future writes carrying the revoked `jti` are gated.
171
+ *
172
+ * Only the original capability issuer may revoke it. The resolver looks up
173
+ * the capability locally and rejects:
174
+ * - `REVOCATION_CAP_UNKNOWN` when no row matches the `jti` on this peer
175
+ * (multi-device-grantor case, where the issuing device is not the one
176
+ * calling `revokeCapability`, is not yet supported).
177
+ * - `REVOCATION_UNAUTHORIZED` when the viewer is not the capability's
178
+ * issuer.
179
+ *
180
+ * Returns the boolean reported by the store: `true` when the revocation
181
+ * row was newly written (or materially changed), `false` on an idempotent
182
+ * no-op (already revoked at an equal or newer HLC).
183
+ */
184
+ revokeCapability: (params: {
185
+ jti: string;
186
+ }) => Promise<boolean>;
132
187
  };
133
188
  export type SyncRequestContext = {
134
189
  addPeer: (config: PeerConfig) => Promise<void>;
@@ -175,12 +230,56 @@ export type HubRequestContext = {
175
230
  }) => Promise<boolean>;
176
231
  listByGroup: (groupID: string) => Promise<Array<HubData>>;
177
232
  listGroups: (hubID: string) => Promise<Array<GroupData>>;
233
+ /** Current health snapshot for a group (state + last transition time). */
234
+ getGroupHealth: (groupID: string) => GroupHealthSnapshot;
235
+ /**
236
+ * Kick off stale-device recovery for a group. Fire-and-forget at the resolver
237
+ * boundary; the terminal outcome arrives via the `groupHealthChanged`
238
+ * subscription. Resolves with the flow's final result for callers that await.
239
+ */
240
+ rejoinGroup: (groupID: string) => Promise<RejoinResult>;
241
+ };
242
+ export type DelegationData = {
243
+ jwt: string;
244
+ jti: string;
245
+ grantor: string;
246
+ audience: string;
247
+ resource: string;
248
+ exp: number;
249
+ };
250
+ export type DelegationTokenCovers = {
251
+ modelID?: string | null;
252
+ docID?: string | null;
253
+ };
254
+ export type ListOwnDelegationTokensParams = {
255
+ viewerDID: string;
256
+ grantor?: string | null;
257
+ covers?: DelegationTokenCovers | null;
258
+ };
259
+ export type RevokedDelegationData = {
260
+ jti: string;
261
+ grantor: string;
262
+ audience: string;
263
+ revokerDID: string;
264
+ revokedAt: number;
265
+ verifiedAt: number;
266
+ capExp: number | null;
267
+ groupID: string;
268
+ };
269
+ export type ListOwnRevokedTokensParams = {
270
+ viewerDID: string;
271
+ grantor?: string | null;
272
+ };
273
+ export type DelegationRequestContext = {
274
+ listOwnTokens: (params: ListOwnDelegationTokensParams) => Promise<Array<DelegationData>>;
275
+ listOwnRevokedTokens: (params: ListOwnRevokedTokensParams) => Promise<Array<RevokedDelegationData>>;
178
276
  };
179
277
  export type P2PContext = {
180
278
  group: GroupRequestContext;
181
279
  sync: SyncRequestContext;
182
280
  join: P2PJoinRequestContext;
183
281
  hub: HubRequestContext;
282
+ delegation: DelegationRequestContext;
184
283
  };
185
284
  declare module '@kubun/graphql' {
186
285
  interface PluginContextMap {
@@ -194,6 +293,28 @@ export type SyncSessionResult = {
194
293
  messagesSent: number;
195
294
  missingClusters?: Record<string, unknown>;
196
295
  };
296
+ /** A group-level health condition the monitor tracks. */
297
+ export type GroupHealthCondition = 'epoch-stale';
298
+ /**
299
+ * Persisted health state for a group. `recovered` is a transient transition
300
+ * surfaced only on the emitted event; `getState` collapses it to `healthy`.
301
+ */
302
+ export type GroupHealthState = 'healthy' | 'degraded' | 'recovering' | 'recovered' | 'recovery-failed';
303
+ export type GroupHealthData = {
304
+ groupID: string;
305
+ condition: GroupHealthCondition;
306
+ state: GroupHealthState;
307
+ };
308
+ /** A point-in-time health snapshot for a group (drives the GraphQL query). */
309
+ export type GroupHealthSnapshot = {
310
+ state: GroupHealthState;
311
+ /** Epoch ms of the last state transition, or null if never transitioned. */
312
+ lastTransitionAt: number | null;
313
+ };
314
+ /** Terminal outcome of a rejoin flow. */
315
+ export type RejoinResult = {
316
+ status: 'recovered' | 'recovery-failed';
317
+ };
197
318
  export type SyncPluginAPI = {
198
319
  /** Resolves when the HTTP sync transport is registered (only present when http option is enabled). */
199
320
  syncReady?: Promise<void>;
package/lib/utils.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /** Node returns a Timeout object with `unref()`; browsers return a number. */
2
+ export declare function unref(timer: ReturnType<typeof setTimeout>): void;
3
+ /** Resolve after `ms`, without keeping the process alive. */
4
+ export declare function sleep(ms: number): Promise<void>;
5
+ export declare function bytesEqual(a: Uint8Array, b: Uint8Array): boolean;
package/lib/utils.js ADDED
@@ -0,0 +1 @@
1
+ export function unref(e){e.unref?.()}export function sleep(e){return new Promise(n=>{unref(setTimeout(n,e))})}export function bytesEqual(e,n){if(e.length!==n.length)return!1;for(let t=0;t<e.length;t++)if(e[t]!==n[t])return!1;return!0}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/plugin-p2p",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "license": "see LICENSE.md",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -14,50 +14,50 @@
14
14
  ],
15
15
  "sideEffects": false,
16
16
  "dependencies": {
17
- "@enkaku/async": "^0.15.0",
18
- "@enkaku/capability": "^0.15.0",
19
- "@enkaku/client": "^0.15.1",
20
- "@enkaku/codec": "^0.15.0",
21
- "@enkaku/event": "^0.15.0",
22
- "@enkaku/generator": "^0.15.0",
23
- "@enkaku/group": "^0.15.0",
24
- "@enkaku/http-client-transport": "^0.15.0",
25
- "@enkaku/http-server-transport": "^0.15.0",
26
- "@enkaku/hub-protocol": "^0.15.0",
27
- "@enkaku/hub-tunnel": "^0.15.0",
28
- "@enkaku/protocol": "^0.15.0",
29
- "@enkaku/runtime": "^0.15.0",
30
- "@enkaku/schema": "^0.15.0",
31
- "@enkaku/server": "^0.15.0",
32
- "@enkaku/stream": "^0.15.0",
33
- "@enkaku/token": "^0.15.0",
34
- "@enkaku/transport": "^0.15.0",
17
+ "@enkaku/async": "^0.16.0",
18
+ "@enkaku/capability": "^0.16.0",
19
+ "@enkaku/client": "^0.16.0",
20
+ "@enkaku/codec": "^0.16.0",
21
+ "@enkaku/event": "^0.16.0",
22
+ "@enkaku/generator": "^0.16.0",
23
+ "@enkaku/group": "^0.16.2",
24
+ "@enkaku/http-client-transport": "^0.16.0",
25
+ "@enkaku/http-server-transport": "^0.16.0",
26
+ "@enkaku/hub-protocol": "^0.16.0",
27
+ "@enkaku/hub-tunnel": "^0.16.0",
28
+ "@enkaku/protocol": "^0.16.0",
29
+ "@enkaku/runtime": "^0.16.0",
30
+ "@enkaku/schema": "^0.16.0",
31
+ "@enkaku/server": "^0.16.0",
32
+ "@enkaku/stream": "^0.16.0",
33
+ "@enkaku/token": "^0.16.0",
34
+ "@enkaku/transport": "^0.16.0",
35
35
  "@noble/hashes": "^2.2.0",
36
- "graphql": "^16.13.2",
37
- "kysely": "^0.28.17",
38
- "@kubun/client": "^0.9.0",
39
- "@kubun/db": "^0.9.0",
40
- "@kubun/db-adapter": "^0.9.0",
41
- "@kubun/engine": "^0.9.0",
42
- "@kubun/store-p2p": "^0.9.0",
43
- "@kubun/store-graph": "^0.9.0",
44
- "@kubun/graphql": "^0.9.0",
45
- "@kubun/hlc": "^0.9.0",
46
- "@kubun/id": "^0.9.0",
47
- "@kubun/logger": "^0.9.0",
48
- "@kubun/mutation": "^0.9.0",
49
- "@kubun/protocol": "^0.9.0"
36
+ "graphql": "^16.14.2",
37
+ "kysely": "^0.29.2",
38
+ "@kubun/client": "^0.10.1",
39
+ "@kubun/db-adapter": "^0.10.0",
40
+ "@kubun/db": "^0.10.0",
41
+ "@kubun/engine": "^0.10.3",
42
+ "@kubun/store-graph": "^0.10.2",
43
+ "@kubun/store-p2p": "^0.10.1",
44
+ "@kubun/hlc": "^0.10.1",
45
+ "@kubun/graphql": "^0.10.1",
46
+ "@kubun/id": "^0.10.0",
47
+ "@kubun/mutation": "^0.10.1",
48
+ "@kubun/logger": "^0.10.0",
49
+ "@kubun/protocol": "^0.10.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@enkaku/hub-server": "^0.15.0",
52
+ "@enkaku/hub-server": "^0.16.0",
53
53
  "get-port": "^7.2.0",
54
- "@kubun/db-node-sqlite": "^0.9.0",
55
- "@kubun/db-better-sqlite": "^0.9.0",
56
- "@kubun/hub": "^0.9.0",
57
- "@kubun/plugin-connector": "^0.9.0",
58
- "@kubun/plugin-http": "^0.9.0",
59
- "@kubun/plugin-rpc": "^0.9.0",
60
- "@kubun/test-utils": "^0.9.0"
54
+ "@kubun/db-better-sqlite": "^0.10.0",
55
+ "@kubun/hub": "^0.10.0",
56
+ "@kubun/plugin-connector": "^0.10.0",
57
+ "@kubun/db-node-sqlite": "^0.10.0",
58
+ "@kubun/plugin-http": "^0.10.0",
59
+ "@kubun/plugin-rpc": "^0.10.0",
60
+ "@kubun/test-utils": "^0.10.0"
61
61
  },
62
62
  "scripts": {
63
63
  "build:clean": "del lib",
@@ -1,13 +0,0 @@
1
- import type { CatalogFilterCriteria, DocumentNode } from '@kubun/protocol';
2
- import type { P2PStoreAPI } from '@kubun/store-p2p';
3
- /**
4
- * Determine whether a document matches a catalog's filter criteria.
5
- *
6
- * Mirrors the semantics of {@link resolveCatalogSyncScopes} (which collects
7
- * broadly-applicable models + owner filters for sync): each catalog field is
8
- * AND'd, values within a field are OR'd, and the `circles` field expands to a
9
- * union of circle members and merges with the explicit `owners` filter.
10
- *
11
- * Returns `true` iff the document would be included by this catalog.
12
- */
13
- export declare function catalogMatchesDoc(criteria: CatalogFilterCriteria, doc: Pick<DocumentNode, 'model' | 'owner'>, p2pStore: P2PStoreAPI): Promise<boolean>;
@@ -1 +0,0 @@
1
- export async function catalogMatchesDoc(e,l,n){if(null!=e.models&&e.models.length>0&&!e.models.includes(l.model))return!1;let c=null!=e.owners&&e.owners.length>0,r=null!=e.circles&&e.circles.length>0;return!!(!c&&!r||c&&e.owners?.includes(l.owner)||r&&null!=e.circles&&await n.isMemberOfAnyCircle(l.owner,e.circles))}