@metalabel/dfos-protocol 0.15.0 → 0.17.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/dist/chunk-XF7T45EQ.js +65 -0
- package/dist/fold/index.d.ts +112 -0
- package/dist/fold/index.js +18 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16 -0
- package/package.json +5 -1
- package/schemas/index.v1.json +71 -0
- package/schemas/profile.v1.json +29 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// src/fold/linearize.ts
|
|
2
|
+
var byteCompare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
3
|
+
var compareHeadPreference = (a, b) => {
|
|
4
|
+
if (a.createdAt !== b.createdAt) return byteCompare(b.createdAt, a.createdAt);
|
|
5
|
+
return byteCompare(b.cid, a.cid);
|
|
6
|
+
};
|
|
7
|
+
var compareLinear = (a, b) => {
|
|
8
|
+
if (a.createdAt !== b.createdAt) return byteCompare(a.createdAt, b.createdAt);
|
|
9
|
+
return byteCompare(a.cid, b.cid);
|
|
10
|
+
};
|
|
11
|
+
var linearize = (ops) => [...ops].sort(compareLinear);
|
|
12
|
+
|
|
13
|
+
// src/fold/lww-map.ts
|
|
14
|
+
var foldLwwMap = (deltas) => {
|
|
15
|
+
const map = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const delta of deltas) {
|
|
17
|
+
if (delta.op === "set") map.set(delta.key, delta.value);
|
|
18
|
+
else map.delete(delta.key);
|
|
19
|
+
}
|
|
20
|
+
return map;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/fold/index-v1.ts
|
|
24
|
+
var INDEX_V1_SCHEMA = "https://schemas.dfos.com/index/v1";
|
|
25
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
26
|
+
var parseIndexDelta = (raw) => {
|
|
27
|
+
if (!isRecord(raw)) return null;
|
|
28
|
+
const { op, key } = raw;
|
|
29
|
+
if (typeof key !== "string") return null;
|
|
30
|
+
if (op === "remove") return { op: "remove", key };
|
|
31
|
+
if (op === "set") {
|
|
32
|
+
const value = raw["value"];
|
|
33
|
+
if (value === void 0) return { op: "set", key };
|
|
34
|
+
if (!isRecord(value)) return null;
|
|
35
|
+
return { op: "set", key, value };
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
};
|
|
39
|
+
var indexDeltaStream = (ops) => {
|
|
40
|
+
const stream = [];
|
|
41
|
+
for (const op of linearize(ops)) {
|
|
42
|
+
const doc = op.document;
|
|
43
|
+
if (!isRecord(doc) || doc["$schema"] !== INDEX_V1_SCHEMA) continue;
|
|
44
|
+
const deltas = doc["deltas"];
|
|
45
|
+
if (!Array.isArray(deltas)) continue;
|
|
46
|
+
for (const raw of deltas) {
|
|
47
|
+
const delta = parseIndexDelta(raw);
|
|
48
|
+
if (!delta) continue;
|
|
49
|
+
if (delta.op === "remove") stream.push({ op: "remove", key: delta.key });
|
|
50
|
+
else stream.push({ op: "set", key: delta.key, value: delta.value ?? {} });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return stream;
|
|
54
|
+
};
|
|
55
|
+
var foldIndexV1 = (ops) => foldLwwMap(indexDeltaStream(ops));
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
byteCompare,
|
|
59
|
+
compareHeadPreference,
|
|
60
|
+
compareLinear,
|
|
61
|
+
linearize,
|
|
62
|
+
foldLwwMap,
|
|
63
|
+
INDEX_V1_SCHEMA,
|
|
64
|
+
foldIndexV1
|
|
65
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte-wise (code-point) string comparison returning -1 | 0 | 1.
|
|
3
|
+
*
|
|
4
|
+
* NOT `localeCompare` — ICU collation is locale/engine dependent with no
|
|
5
|
+
* determinism contract. For base32lower CIDs and ASCII ISO-8601 timestamps,
|
|
6
|
+
* JS `<`/`>` (UTF-16 code-unit order) equals byte-wise order, and equals the
|
|
7
|
+
* Go relay twin's `<`/`>` on the same strings. This is the single comparison
|
|
8
|
+
* primitive both head selection and the canonical fold are built on.
|
|
9
|
+
*/
|
|
10
|
+
declare const byteCompare: (a: string, b: string) => number;
|
|
11
|
+
/** The minimum an operation must expose to be ordered: its CID and `createdAt`. */
|
|
12
|
+
interface OrderKey {
|
|
13
|
+
/** Operation CID — multibase (base32lower) string from the JWS `cid` header. */
|
|
14
|
+
cid: string;
|
|
15
|
+
/** ISO-8601 `createdAt` from the operation payload. */
|
|
16
|
+
createdAt: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Head-preference comparator — the web relay's `selectDeterministicHead` tip
|
|
20
|
+
* ordering, exported so head selection and the canonical fold cannot drift.
|
|
21
|
+
*
|
|
22
|
+
* Sorts the MORE head-preferred operation FIRST: highest `createdAt`, then
|
|
23
|
+
* highest CID (both byte-wise). `array.sort(compareHeadPreference)[0]` is the
|
|
24
|
+
* head among a set of tips.
|
|
25
|
+
*/
|
|
26
|
+
declare const compareHeadPreference: (a: OrderKey, b: OrderKey) => number;
|
|
27
|
+
/**
|
|
28
|
+
* Canonical linearization comparator — the exact reverse of
|
|
29
|
+
* `compareHeadPreference`. Sorts ascending (lowest `createdAt` first, then
|
|
30
|
+
* lowest CID), so the head-preferred operation sorts LAST and, under the LWW
|
|
31
|
+
* fold, is applied last (last-applied wins).
|
|
32
|
+
*/
|
|
33
|
+
declare const compareLinear: (a: OrderKey, b: OrderKey) => number;
|
|
34
|
+
/**
|
|
35
|
+
* Order a set of operations into the canonical total order — createdAt
|
|
36
|
+
* ascending, CID ascending tiebreak, branch-inclusive.
|
|
37
|
+
*
|
|
38
|
+
* Deterministic regardless of input order: CIDs are unique per operation, so
|
|
39
|
+
* the order is a strict total order and any permutation of the same operation
|
|
40
|
+
* set linearizes identically (this is what makes concurrent forks converge).
|
|
41
|
+
* Returns a new array; the input is not mutated.
|
|
42
|
+
*/
|
|
43
|
+
declare const linearize: <T extends OrderKey>(ops: readonly T[]) => T[];
|
|
44
|
+
|
|
45
|
+
/** A single LWW-Map delta over key `key`. */
|
|
46
|
+
type LwwDelta<V = unknown> = {
|
|
47
|
+
readonly op: 'set';
|
|
48
|
+
readonly key: string;
|
|
49
|
+
readonly value: V;
|
|
50
|
+
} | {
|
|
51
|
+
readonly op: 'remove';
|
|
52
|
+
readonly key: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Fold an ordered sequence of deltas into a Map. Deltas are applied in the
|
|
56
|
+
* order given — the caller linearizes first. Insertion order of the returned
|
|
57
|
+
* Map reflects last-set position, but equality is by (key → value) content, so
|
|
58
|
+
* two ingest orders that linearize identically produce equal maps.
|
|
59
|
+
*/
|
|
60
|
+
declare const foldLwwMap: <V = unknown>(deltas: Iterable<LwwDelta<V>>) => Map<string, V>;
|
|
61
|
+
|
|
62
|
+
/** Canonical `$schema` URI for index documents. */
|
|
63
|
+
declare const INDEX_V1_SCHEMA = "https://schemas.dfos.com/index/v1";
|
|
64
|
+
/**
|
|
65
|
+
* An index entry's metadata value. `label` and `order` are the standard hints;
|
|
66
|
+
* unknown keys are preserved (forward compat). The degenerate set-membership
|
|
67
|
+
* case is the empty object `{}`.
|
|
68
|
+
*/
|
|
69
|
+
interface IndexEntry {
|
|
70
|
+
/** Optional display label for the entry. */
|
|
71
|
+
label?: string;
|
|
72
|
+
/** Optional ordering hint (integer, per the content number-encoding rule). */
|
|
73
|
+
order?: number;
|
|
74
|
+
[k: string]: unknown;
|
|
75
|
+
}
|
|
76
|
+
/** A single index/v1 delta. */
|
|
77
|
+
type IndexDelta = {
|
|
78
|
+
op: 'set';
|
|
79
|
+
key: string;
|
|
80
|
+
value?: IndexEntry;
|
|
81
|
+
} | {
|
|
82
|
+
op: 'remove';
|
|
83
|
+
key: string;
|
|
84
|
+
};
|
|
85
|
+
/** An index/v1 document — the content committed by one operation. */
|
|
86
|
+
interface IndexDocument {
|
|
87
|
+
$schema: string;
|
|
88
|
+
deltas: IndexDelta[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* An operation reduced to what the fold needs: its ordering keys plus the
|
|
92
|
+
* resolved content document. `document` is whatever the operation committed by
|
|
93
|
+
* CID (an `IndexDocument` for index chains; anything else is skipped).
|
|
94
|
+
*/
|
|
95
|
+
interface FoldOperation extends OrderKey {
|
|
96
|
+
/** The resolved content document this operation committed (null for delete/clear). */
|
|
97
|
+
document: unknown;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Fold a set of index/v1 operations into the resolved index map.
|
|
101
|
+
*
|
|
102
|
+
* The fold is branch-INCLUSIVE — pass every operation in the chain's log, not
|
|
103
|
+
* just the selected-head branch. Concurrent forks converge: any permutation of
|
|
104
|
+
* the same operation set folds to an equal map.
|
|
105
|
+
*
|
|
106
|
+
* Precondition: the chain is not delete-terminal. If the selected head branch
|
|
107
|
+
* is a `delete`, the chain is deleted and the fold is moot — the caller checks
|
|
108
|
+
* `isDeleted` (from `verifyContentChain`) and does not fold a deleted chain.
|
|
109
|
+
*/
|
|
110
|
+
declare const foldIndexV1: (ops: readonly FoldOperation[]) => Map<string, IndexEntry>;
|
|
111
|
+
|
|
112
|
+
export { type FoldOperation, INDEX_V1_SCHEMA, type IndexDelta, type IndexDocument, type IndexEntry, type LwwDelta, type OrderKey, byteCompare, compareHeadPreference, compareLinear, foldIndexV1, foldLwwMap, linearize };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
INDEX_V1_SCHEMA,
|
|
3
|
+
byteCompare,
|
|
4
|
+
compareHeadPreference,
|
|
5
|
+
compareLinear,
|
|
6
|
+
foldIndexV1,
|
|
7
|
+
foldLwwMap,
|
|
8
|
+
linearize
|
|
9
|
+
} from "../chunk-XF7T45EQ.js";
|
|
10
|
+
export {
|
|
11
|
+
INDEX_V1_SCHEMA,
|
|
12
|
+
byteCompare,
|
|
13
|
+
compareHeadPreference,
|
|
14
|
+
compareLinear,
|
|
15
|
+
foldIndexV1,
|
|
16
|
+
foldLwwMap,
|
|
17
|
+
linearize
|
|
18
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { JwsHeader, JwsVerificationError, JwtClaims, JwtCreateOptions, JwtHeader
|
|
|
2
2
|
export { A as ARTIFACT_CID_ANCHOR_RE, a as ArtifactPayload, C as CONTENT_ID_ANCHOR_RE, b as ContentOperation, c as CountersignPayload, I as IdentityOperation, M as MAX_ARTIFACT_PAYLOAD_SIZE, d as MAX_OPERATION_SIZE, e as MAX_SERVICES_ENTRIES, f as MAX_SERVICES_PAYLOAD_SIZE, g as MultikeyPublicKey, R as RevocationPayload, S as ServiceEntry, h as ServicesArray, i as Signer, V as VerifiedIdentity } from './schemas-BXye25k7.js';
|
|
3
3
|
export { AnchorKind, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, RECOGNIZED_SERVICE_TYPES, VerifiedArtifact, VerifiedContentChain, VerifiedCountersignature, VerifiedRevocation, anchorsByLabel, assertServicesWithinCap, classifyAnchor, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, isRecognizedServiceType, relayEndpoints, signArtifact, signContentOperation, signCountersignature, signIdentityOperation, signRevocation, verifyArtifact, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation } from './chain/index.js';
|
|
4
4
|
export { Attenuation, AuthTokenClaims, AuthTokenCreateOptions, AuthTokenVerificationError, AuthTokenVerifyOptions, CredentialVerificationError, DFOSCredentialPayload, MAX_CREDENTIAL_SIZE, VerifiedAuthToken, VerifiedDFOSCredential, VerifiedDelegationChain, createAuthToken, createDFOSCredential, decodeDFOSCredentialUnsafe, isAttenuated, matchesResource, verifyAuthToken, verifyDFOSCredential, verifyDelegationChain } from './credentials/index.js';
|
|
5
|
+
export { FoldOperation, INDEX_V1_SCHEMA, IndexDelta, IndexDocument, IndexEntry, LwwDelta, OrderKey, byteCompare, compareHeadPreference, compareLinear, foldIndexV1, foldLwwMap, linearize } from './fold/index.js';
|
|
5
6
|
import 'multiformats';
|
|
6
7
|
import 'multiformats/cid';
|
|
7
8
|
import 'zod';
|
package/dist/index.js
CHANGED
|
@@ -79,6 +79,15 @@ import {
|
|
|
79
79
|
verifyJws,
|
|
80
80
|
verifyJwt
|
|
81
81
|
} from "./chunk-4QQ5HK5M.js";
|
|
82
|
+
import {
|
|
83
|
+
INDEX_V1_SCHEMA,
|
|
84
|
+
byteCompare,
|
|
85
|
+
compareHeadPreference,
|
|
86
|
+
compareLinear,
|
|
87
|
+
foldIndexV1,
|
|
88
|
+
foldLwwMap,
|
|
89
|
+
linearize
|
|
90
|
+
} from "./chunk-XF7T45EQ.js";
|
|
82
91
|
export {
|
|
83
92
|
ARTIFACT_CID_ANCHOR_RE,
|
|
84
93
|
ArtifactPayload,
|
|
@@ -92,6 +101,7 @@ export {
|
|
|
92
101
|
DFOSCredentialPayload,
|
|
93
102
|
ED25519_PRIV_MULTICODEC,
|
|
94
103
|
ED25519_PUB_MULTICODEC,
|
|
104
|
+
INDEX_V1_SCHEMA,
|
|
95
105
|
IdentityOperation,
|
|
96
106
|
JwsVerificationError,
|
|
97
107
|
JwtVerificationError,
|
|
@@ -111,7 +121,10 @@ export {
|
|
|
111
121
|
assertServicesWithinCap,
|
|
112
122
|
base64urlDecode,
|
|
113
123
|
base64urlEncode,
|
|
124
|
+
byteCompare,
|
|
114
125
|
classifyAnchor,
|
|
126
|
+
compareHeadPreference,
|
|
127
|
+
compareLinear,
|
|
115
128
|
createAuthToken,
|
|
116
129
|
createDFOSCredential,
|
|
117
130
|
createJws,
|
|
@@ -125,6 +138,8 @@ export {
|
|
|
125
138
|
deriveChainIdentifier,
|
|
126
139
|
deriveContentId,
|
|
127
140
|
encodeEd25519Multikey,
|
|
141
|
+
foldIndexV1,
|
|
142
|
+
foldLwwMap,
|
|
128
143
|
generateId,
|
|
129
144
|
generateIdNoPrefix,
|
|
130
145
|
importEd25519Keypair,
|
|
@@ -133,6 +148,7 @@ export {
|
|
|
133
148
|
isRecognizedServiceType,
|
|
134
149
|
isValidEd25519Signature,
|
|
135
150
|
isValidId,
|
|
151
|
+
linearize,
|
|
136
152
|
matchesResource,
|
|
137
153
|
normalizedId,
|
|
138
154
|
parseDagCborCID,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metalabel/dfos-protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DFOS Protocol — Ed25519 signed chain primitives, services, credentials, and verification",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
"./credentials": {
|
|
41
41
|
"import": "./dist/credentials/index.js",
|
|
42
42
|
"types": "./dist/credentials/index.d.ts"
|
|
43
|
+
},
|
|
44
|
+
"./fold": {
|
|
45
|
+
"import": "./dist/fold/index.js",
|
|
46
|
+
"types": "./dist/fold/index.d.ts"
|
|
43
47
|
}
|
|
44
48
|
},
|
|
45
49
|
"files": [
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.dfos.com/index/v1",
|
|
4
|
+
"title": "Index",
|
|
5
|
+
"description": "An index chain document — an LWW-Map folded via the canonical fold. Each operation's document carries an array of set/remove deltas over content-ref keys. The resolved index is the fold over ALL operations (every branch) in canonical order; the last delta touching a key wins.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["$schema", "deltas"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": {
|
|
10
|
+
"const": "https://schemas.dfos.com/index/v1"
|
|
11
|
+
},
|
|
12
|
+
"deltas": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"description": "Ordered deltas contributed by this operation. Applied in array order within the document, then across documents in canonical fold order.",
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"required": ["op"],
|
|
18
|
+
"properties": {
|
|
19
|
+
"op": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Delta operation. 'set' and 'remove' are the v1 vocabulary; deltas with other ops are valid and are skipped deterministically by readers (forward compat). Validators MUST NOT reject documents carrying additional delta shapes."
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"allOf": [
|
|
25
|
+
{
|
|
26
|
+
"if": { "properties": { "op": { "const": "set" } } },
|
|
27
|
+
"then": {
|
|
28
|
+
"required": ["key"],
|
|
29
|
+
"properties": {
|
|
30
|
+
"key": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Content ref — a 31-char content chain id or a CID."
|
|
33
|
+
},
|
|
34
|
+
"value": { "$ref": "#/$defs/entry" }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"if": { "properties": { "op": { "const": "remove" } } },
|
|
40
|
+
"then": {
|
|
41
|
+
"required": ["key"],
|
|
42
|
+
"properties": {
|
|
43
|
+
"key": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "Content ref to drop from the index."
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"additionalProperties": false,
|
|
55
|
+
"$defs": {
|
|
56
|
+
"entry": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"description": "Optional entry metadata for a set delta. A pure set-membership index uses the degenerate empty object {}. Unknown fields are preserved (forward compat).",
|
|
59
|
+
"properties": {
|
|
60
|
+
"label": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Optional display label."
|
|
63
|
+
},
|
|
64
|
+
"order": {
|
|
65
|
+
"type": "integer",
|
|
66
|
+
"description": "Optional ordering hint (integer per the content number-encoding rule)."
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
package/schemas/profile.v1.json
CHANGED
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
"type": "string",
|
|
18
18
|
"description": "Short bio or description."
|
|
19
19
|
},
|
|
20
|
+
"avatar": {
|
|
21
|
+
"$ref": "#/$defs/media",
|
|
22
|
+
"description": "Optional avatar image as a Media object."
|
|
23
|
+
},
|
|
20
24
|
"links": {
|
|
21
25
|
"type": "array",
|
|
22
26
|
"maxItems": 20,
|
|
@@ -43,5 +47,29 @@
|
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
},
|
|
46
|
-
"additionalProperties": false
|
|
50
|
+
"additionalProperties": false,
|
|
51
|
+
"$defs": {
|
|
52
|
+
"media": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"required": ["uri"],
|
|
55
|
+
"properties": {
|
|
56
|
+
"uri": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"format": "uri",
|
|
59
|
+
"description": "Canonical reference to the media — an attachment://<id> ref (opaque, host-scoped) or any other URI. Always present."
|
|
60
|
+
},
|
|
61
|
+
"cid": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"pattern": "^bafkrei[a-z2-7]{52}$",
|
|
64
|
+
"description": "Optional content commitment — CIDv1, raw codec (0x55), sha2-256, base32 lowercase, computed over the media bytes exactly as stored/served. May be absent when no cid has been computed for the media."
|
|
65
|
+
},
|
|
66
|
+
"href": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"format": "uri",
|
|
69
|
+
"description": "Optional resolution hint — a plain URL where the bytes may currently be fetched. Implementation-dependent and non-normative; carries no integrity promise."
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"additionalProperties": false
|
|
73
|
+
}
|
|
74
|
+
}
|
|
47
75
|
}
|