@serfab/quereus-plugin-sereus 0.8.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 +360 -0
- package/dist/compose-strand.d.ts +84 -0
- package/dist/compose-strand.js +194 -0
- package/dist/compose-strand.js.map +1 -0
- package/dist/connect-browser.d.ts +14 -0
- package/dist/connect-browser.js +45 -0
- package/dist/connect-browser.js.map +1 -0
- package/dist/connect.d.ts +10 -0
- package/dist/connect.js +31 -0
- package/dist/connect.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/parse-config.d.ts +7 -0
- package/dist/parse-config.js +47 -0
- package/dist/parse-config.js.map +1 -0
- package/dist/plugin-browser.d.ts +19 -0
- package/dist/plugin-browser.js +94664 -0
- package/dist/plugin-browser.js.map +7 -0
- package/dist/plugin.d.ts +17 -0
- package/dist/plugin.js +30 -0
- package/dist/plugin.js.map +1 -0
- package/dist/strand-schema.d.ts +30 -0
- package/dist/strand-schema.js +161 -0
- package/dist/strand-schema.js.map +1 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +145 -0
- package/src/compose-strand.ts +314 -0
- package/src/connect-browser.ts +56 -0
- package/src/connect.ts +37 -0
- package/src/index.ts +2 -0
- package/src/parse-config.ts +55 -0
- package/src/plugin-browser.ts +29 -0
- package/src/plugin.ts +39 -0
- package/src/strand-schema.ts +160 -0
- package/src/types.ts +69 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded strand membership/RBAC schema for cross-platform compatibility.
|
|
3
|
+
*
|
|
4
|
+
* This is the authoritative runtime copy of the `Strand` membership schema. It
|
|
5
|
+
* is duplicated from `schemas/strand.qsql` so that React Native and other
|
|
6
|
+
* filesystem-less environments get the schema without a runtime file read —
|
|
7
|
+
* the same pattern `CONTROL_SCHEMA` (cadre-core) uses for the control schema.
|
|
8
|
+
*
|
|
9
|
+
* `STRAND_SCHEMA` holds only the *body* (the inner table declarations). The
|
|
10
|
+
* shared composition (`composeStrand`) wraps it in
|
|
11
|
+
* `declare schema Strand { ... } apply schema Strand;`, mirroring how the sApp
|
|
12
|
+
* schema is wrapped in `declare schema App { ... }`.
|
|
13
|
+
*
|
|
14
|
+
* Drift discipline: the table declarations here MUST stay byte-equivalent to the
|
|
15
|
+
* body of `schemas/strand.qsql` (the on-disk canonical copy). Any edit here must
|
|
16
|
+
* be mirrored there and vice versa — the same invariant the planned
|
|
17
|
+
* `control-schema-drift-guard` enforces for the control schema.
|
|
18
|
+
*
|
|
19
|
+
* Crypto idiom (matches `schemas/control.qsql` and the sApp RBAC fixture
|
|
20
|
+
* `packages/integration-tests/fixtures/simple-sapp.qsql`): every signed write
|
|
21
|
+
* proves itself with `verify(digest(<concatenated payload>, 'sha256', 'utf8'),
|
|
22
|
+
* <signature>, <pubkey>, 'ed25519')`. Member keys are ed25519, so the explicit
|
|
23
|
+
* curve arg is REQUIRED (verify() otherwise defaults to secp256k1).
|
|
24
|
+
*
|
|
25
|
+
* Population note: this ticket applies the schema and makes its constraints
|
|
26
|
+
* active — it does NOT write membership rows at runtime. Inserting the `Header`,
|
|
27
|
+
* the founding `Authority`/`Member`, and the invite/peer flows is owned by
|
|
28
|
+
* `strand-membership-lifecycle-population`.
|
|
29
|
+
*/
|
|
30
|
+
export const STRAND_SCHEMA = ` table Header (
|
|
31
|
+
Id text, -- UUID of this network - generated at inception - immutable
|
|
32
|
+
|
|
33
|
+
-- Types: 'o' = Open, 'c' = Closed
|
|
34
|
+
-- Any peers can join an open network, but only members can join a closed network
|
|
35
|
+
-- Open can still control writes in the app, but only Closed prevents reads
|
|
36
|
+
Type text check (Type in ('o', 'c')),
|
|
37
|
+
|
|
38
|
+
-- The app that this strand faciliates - public key from author to prevent tampering
|
|
39
|
+
sAppId text,
|
|
40
|
+
|
|
41
|
+
-- The version of the sApp that this strand faciliates
|
|
42
|
+
sAppVersion text,
|
|
43
|
+
|
|
44
|
+
-- The schema of the sApp that this strand faciliates
|
|
45
|
+
sAppSchema text,
|
|
46
|
+
|
|
47
|
+
-- The author's signature on the schema to guarantee authorship
|
|
48
|
+
sAppSignature text,
|
|
49
|
+
|
|
50
|
+
-- The engine (rules logic system) that is assumed to manage this strand
|
|
51
|
+
Engine text,
|
|
52
|
+
|
|
53
|
+
-- The version of the engine that is assumed to manage this strand
|
|
54
|
+
EngineVersion text,
|
|
55
|
+
|
|
56
|
+
primary key (/* empty - singleton */),
|
|
57
|
+
constraint InsertOnly check on update, delete (false), -- One-time insert only for now - TODO: revisit for versioning
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
-- An invitation to join a strand as a member
|
|
61
|
+
table Invite (
|
|
62
|
+
Key text primary key,
|
|
63
|
+
Expiration datetime null,
|
|
64
|
+
-- TODO: ability to deactivate?
|
|
65
|
+
constraint InsertOnly check on update, delete (false),
|
|
66
|
+
constraint OnlyClosed check (
|
|
67
|
+
exists (select 1 from Header H where H.Type = 'c')
|
|
68
|
+
),
|
|
69
|
+
constraint InviteValid check on insert (
|
|
70
|
+
-- Can only be inserted by an authority,
|
|
71
|
+
exists (select 1 from Authority A
|
|
72
|
+
where A.MemberKey = context.AuthorityKey
|
|
73
|
+
and verify(digest(new.Key || '|' || coalesce(new.Expiration, ''), 'sha256', 'utf8'), context.AuthoritySignature, A.MemberKey, 'ed25519')
|
|
74
|
+
)
|
|
75
|
+
-- and must also prove invite private key held by issuing authority
|
|
76
|
+
and verify(digest(new.Key || '|' || coalesce(new.Expiration, ''), 'sha256', 'utf8'), context.InviteSignature, new.Key, 'ed25519')
|
|
77
|
+
)
|
|
78
|
+
) with context (AuthorityKey text null, AuthoritySignature text null, InviteSignature text null);
|
|
79
|
+
|
|
80
|
+
-- Invite [InviteKey] has been used to add [MemberKey] as a member
|
|
81
|
+
table ConsumedInvite (
|
|
82
|
+
InviteKey text primary key,
|
|
83
|
+
MemberKey text,
|
|
84
|
+
constraint InsertOnly check on update, delete (false),
|
|
85
|
+
constraint InviteExists check (exists (select 1 from Invite I where I.Key = new.InviteKey)),
|
|
86
|
+
constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),
|
|
87
|
+
constraint ValidUsage check on insert (
|
|
88
|
+
exists (select 1 from Invite I where I.Key = new.InviteKey and verify(digest(new.InviteKey || '|' || new.MemberKey, 'sha256', 'utf8'), context.InviteSignature, new.InviteKey, 'ed25519'))
|
|
89
|
+
),
|
|
90
|
+
constraint MemberValid check (exists (select 1 from Member M where M.Key = new.MemberKey))
|
|
91
|
+
) with context (InviteSignature text null);
|
|
92
|
+
|
|
93
|
+
-- A party in the closed strand network
|
|
94
|
+
table Member (
|
|
95
|
+
Key text primary key,
|
|
96
|
+
constraint NoUpdate check on update (false),
|
|
97
|
+
constraint OnlyClosed check (
|
|
98
|
+
exists (select 1 from Header H where H.Type = 'c')
|
|
99
|
+
),
|
|
100
|
+
constraint Authorized check on insert (
|
|
101
|
+
-- There are no other records - first member needs no authorization
|
|
102
|
+
(select count(1) from Member) <= 1
|
|
103
|
+
|
|
104
|
+
-- or added directly by authority
|
|
105
|
+
or exists (
|
|
106
|
+
select 1 from Authority A
|
|
107
|
+
where A.MemberKey = context.AuthorityKey
|
|
108
|
+
and verify(digest(new.Key, 'sha256', 'utf8'), context.AuthoritySignature, A.MemberKey, 'ed25519')
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
-- or added by invite
|
|
112
|
+
or exists (
|
|
113
|
+
select 1 from ConsumedInvite CI where CI.MemberKey = new.Key
|
|
114
|
+
)
|
|
115
|
+
),
|
|
116
|
+
-- TODO: handle member revocation constraint
|
|
117
|
+
) with context (AuthorityKey text null, AuthoritySignature text null);
|
|
118
|
+
|
|
119
|
+
-- A member-associated peer (node)
|
|
120
|
+
table MemberPeer (
|
|
121
|
+
MemberKey text,
|
|
122
|
+
PeerId text,
|
|
123
|
+
primary key (MemberKey, PeerId),
|
|
124
|
+
constraint MemberExists check (exists (select 1 from Member M where M.Key = new.MemberKey)),
|
|
125
|
+
constraint Authorized check (
|
|
126
|
+
verify(
|
|
127
|
+
digest(coalesce(new.MemberKey, old.MemberKey) || '|' || coalesce(new.PeerId, old.PeerId), 'sha256', 'utf8'),
|
|
128
|
+
context.Signature,
|
|
129
|
+
coalesce(new.MemberKey, old.MemberKey),
|
|
130
|
+
'ed25519'
|
|
131
|
+
)
|
|
132
|
+
),
|
|
133
|
+
) with context (Signature text null);
|
|
134
|
+
|
|
135
|
+
-- An authority is a member that can issue invites, authorize members, and rotate authorities
|
|
136
|
+
table Authority (
|
|
137
|
+
MemberKey text primary key,
|
|
138
|
+
constraint OnlyClosed check (
|
|
139
|
+
exists (select 1 from Header H where H.Type = 'c')
|
|
140
|
+
),
|
|
141
|
+
constraint Authorized check (
|
|
142
|
+
-- There are no existing records - first authority needs no authorization
|
|
143
|
+
(select count(1) from Authority) <= 1
|
|
144
|
+
|
|
145
|
+
-- or authorized by this former authority
|
|
146
|
+
or (
|
|
147
|
+
old.MemberKey is not null
|
|
148
|
+
and old.MemberKey = context.AuthorityKey
|
|
149
|
+
and verify(digest(old.MemberKey, 'sha256', 'utf8'), context.Signature, old.MemberKey, 'ed25519')
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
-- or authorized by another existing authority
|
|
153
|
+
or exists (
|
|
154
|
+
select 1 from Authority A
|
|
155
|
+
where A.MemberKey = context.AuthorityKey
|
|
156
|
+
and verify(digest(coalesce(new.MemberKey, old.MemberKey), 'sha256', 'utf8'), context.Signature, A.MemberKey, 'ed25519')
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
) with context (AuthorityKey text null, Signature text null);
|
|
160
|
+
`;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Libp2p } from '@libp2p/interface';
|
|
2
|
+
import type { IRepo } from '@optimystic/db-core';
|
|
3
|
+
import type { IRawStorage } from '@optimystic/db-p2p';
|
|
4
|
+
|
|
5
|
+
export interface StrandConnectionOptions {
|
|
6
|
+
/** UUID of the strand to connect to */
|
|
7
|
+
strandId: string;
|
|
8
|
+
/** Bootstrap multiaddrs for peer discovery */
|
|
9
|
+
bootstrapNodes?: string[];
|
|
10
|
+
/** sApp schema DDL to apply (optional - omit if schema already exists on strand) */
|
|
11
|
+
schema?: string;
|
|
12
|
+
/** sApp author public key */
|
|
13
|
+
sAppId?: string;
|
|
14
|
+
/** sApp version */
|
|
15
|
+
sAppVersion?: string;
|
|
16
|
+
/** libp2p listening port (default: 0 = random) */
|
|
17
|
+
port?: number;
|
|
18
|
+
/** Enable optimystic caching (default: true) */
|
|
19
|
+
enableCache?: boolean;
|
|
20
|
+
/** FRET profile (default: 'edge') */
|
|
21
|
+
fretProfile?: 'edge' | 'core';
|
|
22
|
+
/** Inject an existing libp2p node instead of creating one */
|
|
23
|
+
libp2pNode?: Libp2p;
|
|
24
|
+
/** Required when libp2pNode is provided */
|
|
25
|
+
coordinatedRepo?: IRepo;
|
|
26
|
+
/**
|
|
27
|
+
* Lifecycle mode. `'networked'` (default) uses the network transactor and
|
|
28
|
+
* is appropriate for multi-peer participation. `'bootstrap'` switches to a
|
|
29
|
+
* local transactor so a solo node can apply schema and accept DML with no
|
|
30
|
+
* peer round trips; pair it with a persistent `storage` to survive restart.
|
|
31
|
+
*/
|
|
32
|
+
mode?: 'bootstrap' | 'networked';
|
|
33
|
+
/**
|
|
34
|
+
* Persistent raw storage. When provided:
|
|
35
|
+
* - it is passed to `createLibp2pNode` as `storage` so the libp2p data path uses it,
|
|
36
|
+
* - in `bootstrap` mode it is also handed to the optimystic plugin as
|
|
37
|
+
* `rawStorageFactory: () => storage` so the local transactor persists DML
|
|
38
|
+
* on the same instance (avoids cache divergence between the two consumers).
|
|
39
|
+
*
|
|
40
|
+
* The plugin treats `storage` as borrowed — it is NOT closed on `shutdown()`.
|
|
41
|
+
*/
|
|
42
|
+
storage?: IRawStorage;
|
|
43
|
+
/** @internal Override transactor type. Used by unit tests with `'test'`. When set, takes precedence only if `mode` is not specified. */
|
|
44
|
+
transactor?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SereusPluginResult {
|
|
48
|
+
vtables: [];
|
|
49
|
+
functions: [];
|
|
50
|
+
collations: [];
|
|
51
|
+
/**
|
|
52
|
+
* Catalog-hydration counts from the warm-restart hydrate that runs before
|
|
53
|
+
* `apply schema App;`. On a cold start (empty storage) both are 0; on a warm
|
|
54
|
+
* restart they report the persisted tables/indexes the catalog was primed
|
|
55
|
+
* with — so a test can assert hydration actually ran. `undefined` only if
|
|
56
|
+
* composition threw before hydrate.
|
|
57
|
+
*/
|
|
58
|
+
hydrated?: { tables: number; indexes: number };
|
|
59
|
+
/** Shuts down the libp2p node and collection factory. Call when done. */
|
|
60
|
+
shutdown: () => Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extended Libp2p node type with coordinatedRepo attached by createLibp2pNode.
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
export interface Libp2pNodeWithRepo extends Libp2p {
|
|
68
|
+
coordinatedRepo: IRepo;
|
|
69
|
+
}
|