@kubun/plugin-p2p 0.12.1 → 0.13.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/lib/context/group.js +3 -0
- package/lib/context/join.js +3 -0
- package/lib/context/peer.js +7 -2
- package/lib/context/sync.js +63 -5
- package/lib/context/types.d.ts +14 -1
- package/lib/context/types.js +12 -0
- package/lib/groups/access-default-apply.d.ts +42 -0
- package/lib/groups/access-default-apply.js +75 -0
- package/lib/groups/broadcast-codec.d.ts +1 -1
- package/lib/groups/broadcast-message.d.ts +240 -0
- package/lib/groups/broadcast-message.js +1 -0
- package/lib/groups/broadcast.d.ts +19 -266
- package/lib/groups/broadcast.js +30 -204
- package/lib/groups/credential-apply.d.ts +82 -0
- package/lib/groups/credential-apply.js +410 -0
- package/lib/groups/credential-grant-token.d.ts +42 -0
- package/lib/groups/credential-grant-token.js +51 -0
- package/lib/groups/credential-grant.d.ts +98 -0
- package/lib/groups/credential-grant.js +165 -0
- package/lib/groups/group-handlers.js +25 -2
- package/lib/groups/group-mls.d.ts +5 -0
- package/lib/groups/group-mls.js +4 -1
- package/lib/groups/group-peer-manager.d.ts +7 -1
- package/lib/groups/group-peer-manager.js +44 -1
- package/lib/groups/group-protocols.d.ts +227 -0
- package/lib/groups/group-protocols.js +146 -0
- package/lib/groups/join-utils.d.ts +5 -0
- package/lib/groups/join-utils.js +5 -1
- package/lib/groups/ledger-adopt.d.ts +84 -0
- package/lib/groups/ledger-adopt.js +142 -0
- package/lib/groups/ledger-commit-fold.d.ts +3 -1
- package/lib/groups/ledger-commit-fold.js +3 -0
- package/lib/groups/ledger-ingest.d.ts +23 -1
- package/lib/groups/ledger-ingest.js +30 -1
- package/lib/groups/manager.d.ts +5 -0
- package/lib/groups/manager.js +6 -1
- package/lib/groups/peer-presence.d.ts +9 -2
- package/lib/groups/peer-presence.js +14 -2
- package/lib/groups/peer-selection.d.ts +9 -0
- package/lib/groups/peer-selection.js +10 -0
- package/lib/hub/wiring.d.ts +6 -1
- package/lib/hub/wiring.js +2 -1
- package/lib/index.d.ts +5 -1
- package/lib/index.js +58 -6
- package/lib/peer/blob-fetch.d.ts +45 -0
- package/lib/peer/blob-fetch.js +89 -0
- package/lib/peer/blob-handlers.d.ts +11 -0
- package/lib/peer/blob-handlers.js +123 -0
- package/lib/peer/controller-fetch.d.ts +6 -0
- package/lib/peer/controller-fetch.js +59 -0
- package/lib/peer/controller-handlers.d.ts +10 -0
- package/lib/peer/controller-handlers.js +28 -0
- package/lib/protocol.d.ts +279 -0
- package/lib/protocol.js +358 -0
- package/lib/sync/access-default-sender.d.ts +1 -1
- package/lib/sync/broadcast-queue.d.ts +1 -1
- package/lib/sync/forwarder.d.ts +1 -1
- package/lib/sync/handlers.js +98 -1
- package/lib/sync/sync-manager.d.ts +10 -1
- package/lib/sync/sync-manager.js +24 -2
- package/lib/types.d.ts +18 -0
- package/package.json +41 -32
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Logger } from '@kubun/logger';
|
|
2
|
-
import type { GroupBroadcastMessage, MutationApplyEntry } from '../groups/broadcast.js';
|
|
2
|
+
import type { GroupBroadcastMessage, MutationApplyEntry } from '../groups/broadcast-message.js';
|
|
3
3
|
/**
|
|
4
4
|
* Configuration for the per-target-group batch broadcast queue.
|
|
5
5
|
*
|
package/lib/sync/forwarder.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Logger } from '@kubun/logger';
|
|
|
3
3
|
import type { DocumentNode } from '@kubun/protocol';
|
|
4
4
|
import type { GraphStoreAPI } from '@kubun/store-graph';
|
|
5
5
|
import type { P2PStoreAPI } from '@kubun/store-p2p';
|
|
6
|
-
import type { GroupBroadcastMessage, MutationApplyEntry } from '../groups/broadcast.js';
|
|
6
|
+
import type { GroupBroadcastMessage, MutationApplyEntry } from '../groups/broadcast-message.js';
|
|
7
7
|
export declare function rethrowIfProgrammerError(error: unknown): void;
|
|
8
8
|
/**
|
|
9
9
|
* Context passed to a {@link ForwardFilter} for each candidate forwarding scope.
|
package/lib/sync/handlers.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { CREDENTIAL_STORE, getCredentialStore } from '@kubun/store-credential';
|
|
1
2
|
import { createDelegationRevocationChecker, getDelegationStore } from '@kubun/store-delegation';
|
|
2
3
|
import { getGraphStore } from '@kubun/store-graph';
|
|
3
4
|
import { getP2PStore } from '@kubun/store-p2p';
|
|
5
|
+
import { verifiedCaller } from '../context/types.js';
|
|
6
|
+
import { readCredentialKeyBundle } from '../groups/credential-grant.js';
|
|
4
7
|
import { wrapHandlers } from '../util/handler-error.js';
|
|
5
8
|
import { authorizeScope, checkSyncDelegation } from './authorize.js';
|
|
6
9
|
import { resolveCatalogSyncScopes } from './catalog-scope.js';
|
|
@@ -8,6 +11,15 @@ import { SyncAccessDeniedError } from './errors.js';
|
|
|
8
11
|
import { createMerkleSyncChannelHandler } from './merkle-channel.js';
|
|
9
12
|
import { negotiateDirection } from './peer-registry.js';
|
|
10
13
|
export { checkSyncDelegation };
|
|
14
|
+
/** The stored op row as the reconcile lane ships it — the same leaf a grant carries. */ function toWireOp(row) {
|
|
15
|
+
return {
|
|
16
|
+
keyID: row.key_id,
|
|
17
|
+
authorDID: row.author_did,
|
|
18
|
+
hlc: row.hlc,
|
|
19
|
+
opJWT: row.op_jwt,
|
|
20
|
+
opHash: row.op_hash
|
|
21
|
+
};
|
|
22
|
+
}
|
|
11
23
|
// ---- Handler factory ----
|
|
12
24
|
/**
|
|
13
25
|
* Create sync protocol handlers served by plugin-p2p's own Enkaku server.
|
|
@@ -195,6 +207,91 @@ export { checkSyncDelegation };
|
|
|
195
207
|
defaultAccessLevel: params.defaultAccessLevel,
|
|
196
208
|
getRevocationChecker,
|
|
197
209
|
getCircleReadGrant
|
|
198
|
-
})
|
|
210
|
+
}),
|
|
211
|
+
'sync/reconcile-credentials': async (ctx)=>{
|
|
212
|
+
const callerDID = verifiedCaller(ctx.message.payload);
|
|
213
|
+
// The credential store is registered by `@kubun/plugin-credential`, not by
|
|
214
|
+
// this plugin, so a device without it has nothing to serve and answers
|
|
215
|
+
// empty rather than throwing (`getStore` on an unregistered name throws).
|
|
216
|
+
if (!db.hasStore(CREDENTIAL_STORE)) {
|
|
217
|
+
return {
|
|
218
|
+
bundles: [],
|
|
219
|
+
tombstones: []
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const store = await getCredentialStore(db);
|
|
223
|
+
const held = new Map(ctx.param.held.map((subject)=>[
|
|
224
|
+
`${subject.subjectKind}\u0000${subject.subjectID}`,
|
|
225
|
+
subject.opHash
|
|
226
|
+
]));
|
|
227
|
+
// Catch-up in two parts, both recipient-scoped to the caller's live
|
|
228
|
+
// wrappings at the current key version. An UNHELD wrapping is a missed
|
|
229
|
+
// grant: ship the whole key bundle. A HELD wrapping means the grant landed,
|
|
230
|
+
// so ship the same bundle carrying only the current-version entries the
|
|
231
|
+
// caller has NOT advertised — an entry added to a key after the grant then
|
|
232
|
+
// reaches an existing recipient (there is no live entry lane). The wrapping
|
|
233
|
+
// is the gate on both paths; a co-recipient's wrapping never travels.
|
|
234
|
+
const scoped = await store.listOpsForRequester(callerDID);
|
|
235
|
+
const wrappingOps = scoped.filter((op)=>op.subject_kind === 'wrapping' && op.deleted === 0);
|
|
236
|
+
const bundles = [];
|
|
237
|
+
for (const wrappingOp of wrappingOps){
|
|
238
|
+
const wrapping = await store.getWrapping(wrappingOp.subject_id);
|
|
239
|
+
if (wrapping?.recipient_did == null) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const key = await store.getKey(wrappingOp.key_id);
|
|
243
|
+
// A superseded wrapping's key has moved on; the current-version wrapping
|
|
244
|
+
// is what a catch-up carries, and its own op routes here when unheld.
|
|
245
|
+
if (key == null || wrapping.key_version !== key.version) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const wrappingHeld = held.get(`wrapping\u0000${wrappingOp.subject_id}`) === wrappingOp.op_hash;
|
|
249
|
+
const keyOp = scoped.find((op)=>op.subject_kind === 'key' && op.subject_id === wrappingOp.key_id);
|
|
250
|
+
const keyHeld = keyOp != null && held.get(`key\u0000${wrappingOp.key_id}`) === keyOp.op_hash;
|
|
251
|
+
try {
|
|
252
|
+
const bundle = await readCredentialKeyBundle(store, wrappingOp.key_id, wrapping.recipient_did);
|
|
253
|
+
if (wrappingHeld && keyHeld) {
|
|
254
|
+
// Held wrapping: the entry top-up. Only entries the caller lacks; a
|
|
255
|
+
// held key with nothing new to send is skipped, not shipped empty.
|
|
256
|
+
const missingEntries = bundle.entries.filter((entry)=>{
|
|
257
|
+
const entryOp = scoped.find((op)=>op.subject_kind === 'entry' && op.subject_id === entry.entryID);
|
|
258
|
+
return entryOp == null || held.get(`entry\u0000${entry.entryID}`) !== entryOp.op_hash;
|
|
259
|
+
});
|
|
260
|
+
if (missingEntries.length === 0) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
bundles.push({
|
|
264
|
+
...bundle,
|
|
265
|
+
entries: missingEntries
|
|
266
|
+
});
|
|
267
|
+
} else {
|
|
268
|
+
bundles.push(bundle);
|
|
269
|
+
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
// A rotation between the scan and this read leaves a key momentarily
|
|
272
|
+
// unreadable; skip it rather than fail the whole reconcile.
|
|
273
|
+
logger.debug('sync/reconcile-credentials: key not readable, skipped', {
|
|
274
|
+
keyID: wrappingOp.key_id,
|
|
275
|
+
error
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// GC: a revoked wrapping's tombstone names no recipient once its row is
|
|
280
|
+
// gone, so it reaches the caller only when the caller advertises the id.
|
|
281
|
+
const allOps = await store.listOps();
|
|
282
|
+
const tombstones = allOps.filter((op)=>op.deleted === 1 && held.has(`${op.subject_kind}\u0000${op.subject_id}`)).map((op)=>({
|
|
283
|
+
...toWireOp(op),
|
|
284
|
+
subjectID: op.subject_id
|
|
285
|
+
}));
|
|
286
|
+
logger.debug('sync/reconcile-credentials served', {
|
|
287
|
+
callerDID,
|
|
288
|
+
bundles: bundles.length,
|
|
289
|
+
tombstones: tombstones.length
|
|
290
|
+
});
|
|
291
|
+
return {
|
|
292
|
+
bundles,
|
|
293
|
+
tombstones
|
|
294
|
+
};
|
|
295
|
+
}
|
|
199
296
|
});
|
|
200
297
|
}
|
|
@@ -3,7 +3,7 @@ import type { StoreProvider } from '@kubun/db';
|
|
|
3
3
|
import type { DefaultAccessLevel, GraphInternals } from '@kubun/engine';
|
|
4
4
|
import type { Logger } from '@kubun/logger';
|
|
5
5
|
import type { Runtime } from '@sozai/runtime';
|
|
6
|
-
import type { SyncDirection } from '../protocol.js';
|
|
6
|
+
import type { SyncDirection, SyncReconcileCredentialsParam, SyncReconcileCredentialsResult } from '../protocol.js';
|
|
7
7
|
import type { StoreUnreadableMode } from '../types.js';
|
|
8
8
|
import { type PeerConfig, type PeerConfigWithID, PeerRegistry } from './peer-registry.js';
|
|
9
9
|
import { type SyncScope, type SyncServerResolver } from './sync-client.js';
|
|
@@ -56,6 +56,14 @@ export type MerkleSyncParams = {
|
|
|
56
56
|
* grants it nothing.
|
|
57
57
|
*/
|
|
58
58
|
endpoint?: string;
|
|
59
|
+
/**
|
|
60
|
+
* The caller's live credential subject ids, present when a credential catch-up
|
|
61
|
+
* piggybacks this doc sync. When set, a `sync/reconcile-credentials` request
|
|
62
|
+
* rides the SAME session after the peer answers negotiate, so no second tunnel
|
|
63
|
+
* is dialled. Its result comes back as `credentialReconcile`; a failure there
|
|
64
|
+
* is swallowed — the credential lane must never fail the doc sync.
|
|
65
|
+
*/
|
|
66
|
+
reconcileCredentialsHeld?: SyncReconcileCredentialsParam['held'];
|
|
59
67
|
};
|
|
60
68
|
export declare class SyncManager {
|
|
61
69
|
#private;
|
|
@@ -97,6 +105,7 @@ export declare class SyncManager {
|
|
|
97
105
|
messagesReceived: number;
|
|
98
106
|
messagesSent: number;
|
|
99
107
|
missingClusters?: Record<string, unknown>;
|
|
108
|
+
credentialReconcile?: SyncReconcileCredentialsResult;
|
|
100
109
|
}>;
|
|
101
110
|
getStatus(params: {
|
|
102
111
|
peerDID?: string;
|
package/lib/sync/sync-manager.js
CHANGED
|
@@ -126,6 +126,26 @@ export class SyncManager {
|
|
|
126
126
|
direction
|
|
127
127
|
});
|
|
128
128
|
const { acceptedScopes, excludedDocumentIDs, missingClusters, direction: agreedDirection } = await syncClient.negotiate(client, scopes, delegationTokens, knownModelIDs, direction);
|
|
129
|
+
// The peer answered, so the session is live: ride the credential catch-up
|
|
130
|
+
// on it now rather than dialling a second tunnel. A responder locks to one
|
|
131
|
+
// dialing session until it idles out, so a fresh dial to the same peer
|
|
132
|
+
// would hang — reuse is the only prompt path. A failure is swallowed: the
|
|
133
|
+
// doc sync must not fail for the credential lane.
|
|
134
|
+
let credentialReconcile;
|
|
135
|
+
if (params.reconcileCredentialsHeld != null) {
|
|
136
|
+
try {
|
|
137
|
+
credentialReconcile = await client.request('sync/reconcile-credentials', {
|
|
138
|
+
param: {
|
|
139
|
+
held: params.reconcileCredentialsHeld
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
} catch (error) {
|
|
143
|
+
this.#logger.warn('credential reconcile over sync session failed', {
|
|
144
|
+
peerDID,
|
|
145
|
+
error
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
129
149
|
if (acceptedScopes.length === 0) {
|
|
130
150
|
this.#logger.info('No scopes accepted by peer');
|
|
131
151
|
this.#lastSyncByPeer.set(peerDID, Date.now());
|
|
@@ -140,7 +160,8 @@ export class SyncManager {
|
|
|
140
160
|
divergentBuckets: 0,
|
|
141
161
|
messagesReceived: 0,
|
|
142
162
|
messagesSent: 0,
|
|
143
|
-
missingClusters
|
|
163
|
+
missingClusters,
|
|
164
|
+
credentialReconcile
|
|
144
165
|
};
|
|
145
166
|
}
|
|
146
167
|
// Step 1b: Deploy missing clusters before building merkle tree
|
|
@@ -221,7 +242,8 @@ export class SyncManager {
|
|
|
221
242
|
divergentBuckets: syncResult.divergentBuckets,
|
|
222
243
|
messagesReceived: syncResult.mutationJWTs.length,
|
|
223
244
|
messagesSent: syncResult.mutationsSent,
|
|
224
|
-
missingClusters
|
|
245
|
+
missingClusters,
|
|
246
|
+
credentialReconcile
|
|
225
247
|
};
|
|
226
248
|
} catch (error) {
|
|
227
249
|
// DEBUG, and re-thrown: this layer cannot know whether the failure is a
|
package/lib/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClientTransportOf } from '@enkaku/protocol';
|
|
2
|
+
import type { SignedEvent } from '@kokuin/controller';
|
|
2
3
|
import type { AccessLevel } from '@kubun/store-graph';
|
|
3
4
|
import type { ControlRequestKind, ControlRequestOutcome } from '@kubun/store-p2p';
|
|
4
5
|
import type { PeerAvailability, PeerCapability } from './groups/group-protocols.js';
|
|
@@ -1019,4 +1020,21 @@ export type SyncPluginAPI = {
|
|
|
1019
1020
|
* this held connection.
|
|
1020
1021
|
*/
|
|
1021
1022
|
getPeerConnection(peerDID: string): PeerConnection | undefined;
|
|
1023
|
+
/**
|
|
1024
|
+
* Pull a remote blob's bytes from a connected peer that holds them, over the
|
|
1025
|
+
* dedicated transfer lane (control plane + raw-HTTP data plane). A no-op if the
|
|
1026
|
+
* bytes are already local; resumable across sessions. `delegationTokens` proves
|
|
1027
|
+
* a cross-owner read; empty for the own-device case.
|
|
1028
|
+
*/
|
|
1029
|
+
fetchAttachment(attachmentID: string, options?: {
|
|
1030
|
+
delegationTokens?: Array<string>;
|
|
1031
|
+
}): Promise<void>;
|
|
1032
|
+
/**
|
|
1033
|
+
* Pull a `did:kokuin:` controller's key-event log from a connected group peer
|
|
1034
|
+
* that holds it, over `controller/get-log`. The engine calls this on a
|
|
1035
|
+
* verify-time controller-store miss; the events self-authenticate, so the
|
|
1036
|
+
* caller's resolver folds + verifies them. `undefined` when no connected peer
|
|
1037
|
+
* answers with a non-empty log.
|
|
1038
|
+
*/
|
|
1039
|
+
fetchControllerLog(did: string): Promise<Array<SignedEvent> | undefined>;
|
|
1022
1040
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/plugin-p2p",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"license": "see LICENSE.md",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -18,17 +18,18 @@
|
|
|
18
18
|
"@enkaku/http-fetch": "^0.21.0",
|
|
19
19
|
"@enkaku/http-serve": "^0.21.0",
|
|
20
20
|
"@enkaku/protocol": "^0.21.0",
|
|
21
|
-
"@enkaku/server": "^0.21.
|
|
21
|
+
"@enkaku/server": "^0.21.2",
|
|
22
22
|
"@enkaku/transport": "^0.21.0",
|
|
23
|
-
"@kokuin/capability": "^0.
|
|
24
|
-
"@kokuin/
|
|
25
|
-
"@
|
|
26
|
-
"@kumiai/
|
|
27
|
-
"@kumiai/hub-
|
|
28
|
-
"@kumiai/hub-
|
|
29
|
-
"@kumiai/
|
|
30
|
-
"@kumiai/mls
|
|
31
|
-
"@kumiai/rpc": "^0.
|
|
23
|
+
"@kokuin/capability": "^0.3.0",
|
|
24
|
+
"@kokuin/controller": "^0.1.0",
|
|
25
|
+
"@kokuin/token": "^0.5.0",
|
|
26
|
+
"@kumiai/broadcast": "^0.7.0",
|
|
27
|
+
"@kumiai/hub-protocol": "^0.7.0",
|
|
28
|
+
"@kumiai/hub-server": "^0.7.0",
|
|
29
|
+
"@kumiai/hub-tunnel": "^0.7.0",
|
|
30
|
+
"@kumiai/mls": "^0.7.0",
|
|
31
|
+
"@kumiai/mls-rpc": "^0.7.0",
|
|
32
|
+
"@kumiai/rpc": "^0.7.0",
|
|
32
33
|
"@noble/ciphers": "^2.3.0",
|
|
33
34
|
"@noble/hashes": "^2.3.0",
|
|
34
35
|
"@sozai/async": "^0.2.1",
|
|
@@ -41,34 +42,42 @@
|
|
|
41
42
|
"graphql": "^16.14.2",
|
|
42
43
|
"kysely": "^0.29.5",
|
|
43
44
|
"ts-mls": "2.0.0-rc.13",
|
|
44
|
-
"@kubun/db": "^0.
|
|
45
|
+
"@kubun/db": "^0.13.0",
|
|
45
46
|
"@kubun/db-adapter": "^0.13.0",
|
|
46
|
-
"@kubun/engine": "^0.
|
|
47
|
+
"@kubun/engine": "^0.13.0",
|
|
47
48
|
"@kubun/graphql": "^0.13.0",
|
|
48
|
-
"@kubun/hlc": "^0.
|
|
49
|
-
"@kubun/http-util": "^0.
|
|
50
|
-
"@kubun/id": "^0.
|
|
51
|
-
"@kubun/logger": "^0.
|
|
52
|
-
"@kubun/
|
|
53
|
-
"@kubun/
|
|
54
|
-
"@kubun/plugin-
|
|
49
|
+
"@kubun/hlc": "^0.13.0",
|
|
50
|
+
"@kubun/http-util": "^0.13.0",
|
|
51
|
+
"@kubun/id": "^0.13.0",
|
|
52
|
+
"@kubun/logger": "^0.13.0",
|
|
53
|
+
"@kubun/plugin-http": "^0.13.0",
|
|
54
|
+
"@kubun/mutation": "^0.13.0",
|
|
55
|
+
"@kubun/plugin-rpc": "^0.13.0",
|
|
55
56
|
"@kubun/protocol": "^0.13.0",
|
|
56
|
-
"@kubun/store-
|
|
57
|
-
"@kubun/store-
|
|
58
|
-
"@kubun/store-
|
|
57
|
+
"@kubun/store-blob": "^0.13.0",
|
|
58
|
+
"@kubun/store-controller": "^0.13.0",
|
|
59
|
+
"@kubun/store-credential": "^0.13.0",
|
|
60
|
+
"@kubun/store-delegation": "^0.13.0",
|
|
61
|
+
"@kubun/store-graph": "^0.13.0",
|
|
62
|
+
"@kubun/store-p2p": "^0.13.0",
|
|
63
|
+
"@kubun/credential": "^0.13.0"
|
|
59
64
|
},
|
|
60
65
|
"devDependencies": {
|
|
61
|
-
"@kumiai/hub-conformance": "^0.
|
|
62
|
-
"@kumiai/rpc-conformance": "^0.
|
|
66
|
+
"@kumiai/hub-conformance": "^0.7.0",
|
|
67
|
+
"@kumiai/rpc-conformance": "^0.7.0",
|
|
63
68
|
"@testcontainers/postgresql": "^12.1.0",
|
|
64
69
|
"get-port": "^7.2.0",
|
|
65
|
-
"@kubun/
|
|
66
|
-
"@kubun/
|
|
67
|
-
"@kubun/db-
|
|
68
|
-
"@kubun/
|
|
69
|
-
"@kubun/
|
|
70
|
-
"@kubun/plugin-
|
|
71
|
-
"@kubun/
|
|
70
|
+
"@kubun/blob-backend": "^0.13.0",
|
|
71
|
+
"@kubun/client": "^0.13.0",
|
|
72
|
+
"@kubun/db-node-sqlite": "^0.13.0",
|
|
73
|
+
"@kubun/db-postgres": "^0.13.0",
|
|
74
|
+
"@kubun/hub": "^0.13.0",
|
|
75
|
+
"@kubun/plugin-blob": "^0.13.0",
|
|
76
|
+
"@kubun/plugin-connector": "^0.13.0",
|
|
77
|
+
"@kubun/plugin-workflow": "^0.13.0",
|
|
78
|
+
"@kubun/test-utils": "^0.13.0",
|
|
79
|
+
"@kubun/store-workflow": "^0.13.0",
|
|
80
|
+
"@kubun/db-better-sqlite": "^0.13.0"
|
|
72
81
|
},
|
|
73
82
|
"publishConfig": {
|
|
74
83
|
"access": "public"
|