@bcts/provenance-mark 1.0.0-alpha.10
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 +48 -0
- package/README.md +15 -0
- package/dist/index.cjs +1703 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +711 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +711 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +1700 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +1657 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +78 -0
- package/src/crypto-utils.ts +75 -0
- package/src/date.ts +216 -0
- package/src/error.ts +141 -0
- package/src/generator.ts +182 -0
- package/src/index.ts +90 -0
- package/src/mark-info.ts +126 -0
- package/src/mark.ts +597 -0
- package/src/resolution.ts +294 -0
- package/src/rng-state.ts +68 -0
- package/src/seed.ts +98 -0
- package/src/utils.ts +103 -0
- package/src/validate.ts +449 -0
- package/src/xoshiro256starstar.ts +150 -0
package/src/error.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Ported from provenance-mark-rust/src/error.rs
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Error types for Provenance Mark operations.
|
|
5
|
+
*/
|
|
6
|
+
export enum ProvenanceMarkErrorType {
|
|
7
|
+
/** Invalid Seed length */
|
|
8
|
+
InvalidSeedLength = "InvalidSeedLength",
|
|
9
|
+
/** Duplicate key */
|
|
10
|
+
DuplicateKey = "DuplicateKey",
|
|
11
|
+
/** Missing key */
|
|
12
|
+
MissingKey = "MissingKey",
|
|
13
|
+
/** Invalid key */
|
|
14
|
+
InvalidKey = "InvalidKey",
|
|
15
|
+
/** Extra keys */
|
|
16
|
+
ExtraKeys = "ExtraKeys",
|
|
17
|
+
/** Invalid key length for the given resolution */
|
|
18
|
+
InvalidKeyLength = "InvalidKeyLength",
|
|
19
|
+
/** Invalid next key length for the given resolution */
|
|
20
|
+
InvalidNextKeyLength = "InvalidNextKeyLength",
|
|
21
|
+
/** Invalid chain ID length for the given resolution */
|
|
22
|
+
InvalidChainIdLength = "InvalidChainIdLength",
|
|
23
|
+
/** Invalid message length for the given resolution */
|
|
24
|
+
InvalidMessageLength = "InvalidMessageLength",
|
|
25
|
+
/** Invalid CBOR data in info field */
|
|
26
|
+
InvalidInfoCbor = "InvalidInfoCbor",
|
|
27
|
+
/** Date out of range for serialization */
|
|
28
|
+
DateOutOfRange = "DateOutOfRange",
|
|
29
|
+
/** Invalid date components */
|
|
30
|
+
InvalidDate = "InvalidDate",
|
|
31
|
+
/** Missing required URL parameter */
|
|
32
|
+
MissingUrlParameter = "MissingUrlParameter",
|
|
33
|
+
/** Year out of range for 2-byte serialization */
|
|
34
|
+
YearOutOfRange = "YearOutOfRange",
|
|
35
|
+
/** Invalid month or day */
|
|
36
|
+
InvalidMonthOrDay = "InvalidMonthOrDay",
|
|
37
|
+
/** Resolution serialization error */
|
|
38
|
+
ResolutionError = "ResolutionError",
|
|
39
|
+
/** Bytewords encoding/decoding error */
|
|
40
|
+
BytewordsError = "BytewordsError",
|
|
41
|
+
/** CBOR encoding/decoding error */
|
|
42
|
+
CborError = "CborError",
|
|
43
|
+
/** URL parsing error */
|
|
44
|
+
UrlError = "UrlError",
|
|
45
|
+
/** Base64 decoding error */
|
|
46
|
+
Base64Error = "Base64Error",
|
|
47
|
+
/** JSON serialization error */
|
|
48
|
+
JsonError = "JsonError",
|
|
49
|
+
/** Integer conversion error */
|
|
50
|
+
IntegerConversionError = "IntegerConversionError",
|
|
51
|
+
/** Validation error */
|
|
52
|
+
ValidationError = "ValidationError",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Error class for Provenance Mark operations.
|
|
57
|
+
*/
|
|
58
|
+
export class ProvenanceMarkError extends Error {
|
|
59
|
+
readonly type: ProvenanceMarkErrorType;
|
|
60
|
+
readonly details?: Record<string, unknown> | undefined;
|
|
61
|
+
|
|
62
|
+
constructor(type: ProvenanceMarkErrorType, message?: string, details?: Record<string, unknown>) {
|
|
63
|
+
const fullMessage =
|
|
64
|
+
message !== undefined && message !== ""
|
|
65
|
+
? `${type}: ${message}`
|
|
66
|
+
: ProvenanceMarkError.defaultMessage(type, details);
|
|
67
|
+
super(fullMessage);
|
|
68
|
+
this.name = "ProvenanceMarkError";
|
|
69
|
+
this.type = type;
|
|
70
|
+
this.details = details;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private static defaultMessage(
|
|
74
|
+
type: ProvenanceMarkErrorType,
|
|
75
|
+
details?: Record<string, unknown>,
|
|
76
|
+
): string {
|
|
77
|
+
const d = (key: string): string => {
|
|
78
|
+
const value = details?.[key];
|
|
79
|
+
if (value === undefined || value === null) return "?";
|
|
80
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
81
|
+
return String(value);
|
|
82
|
+
}
|
|
83
|
+
return JSON.stringify(value);
|
|
84
|
+
};
|
|
85
|
+
switch (type) {
|
|
86
|
+
case ProvenanceMarkErrorType.InvalidSeedLength:
|
|
87
|
+
return `invalid seed length: expected 32 bytes, got ${d("actual")} bytes`;
|
|
88
|
+
case ProvenanceMarkErrorType.DuplicateKey:
|
|
89
|
+
return `duplicate key: ${d("key")}`;
|
|
90
|
+
case ProvenanceMarkErrorType.MissingKey:
|
|
91
|
+
return `missing key: ${d("key")}`;
|
|
92
|
+
case ProvenanceMarkErrorType.InvalidKey:
|
|
93
|
+
return `invalid key: ${d("key")}`;
|
|
94
|
+
case ProvenanceMarkErrorType.ExtraKeys:
|
|
95
|
+
return `wrong number of keys: expected ${d("expected")}, got ${d("actual")}`;
|
|
96
|
+
case ProvenanceMarkErrorType.InvalidKeyLength:
|
|
97
|
+
return `invalid key length: expected ${d("expected")}, got ${d("actual")}`;
|
|
98
|
+
case ProvenanceMarkErrorType.InvalidNextKeyLength:
|
|
99
|
+
return `invalid next key length: expected ${d("expected")}, got ${d("actual")}`;
|
|
100
|
+
case ProvenanceMarkErrorType.InvalidChainIdLength:
|
|
101
|
+
return `invalid chain ID length: expected ${d("expected")}, got ${d("actual")}`;
|
|
102
|
+
case ProvenanceMarkErrorType.InvalidMessageLength:
|
|
103
|
+
return `invalid message length: expected at least ${d("expected")}, got ${d("actual")}`;
|
|
104
|
+
case ProvenanceMarkErrorType.InvalidInfoCbor:
|
|
105
|
+
return "invalid CBOR data in info field";
|
|
106
|
+
case ProvenanceMarkErrorType.DateOutOfRange:
|
|
107
|
+
return `date out of range: ${d("details")}`;
|
|
108
|
+
case ProvenanceMarkErrorType.InvalidDate:
|
|
109
|
+
return `invalid date: ${d("details")}`;
|
|
110
|
+
case ProvenanceMarkErrorType.MissingUrlParameter:
|
|
111
|
+
return `missing required URL parameter: ${d("parameter")}`;
|
|
112
|
+
case ProvenanceMarkErrorType.YearOutOfRange:
|
|
113
|
+
return `year out of range for 2-byte serialization: must be between 2023-2150, got ${d("year")}`;
|
|
114
|
+
case ProvenanceMarkErrorType.InvalidMonthOrDay:
|
|
115
|
+
return `invalid month (${d("month")}) or day (${d("day")}) for year ${d("year")}`;
|
|
116
|
+
case ProvenanceMarkErrorType.ResolutionError:
|
|
117
|
+
return `resolution serialization error: ${d("details")}`;
|
|
118
|
+
case ProvenanceMarkErrorType.BytewordsError:
|
|
119
|
+
return `bytewords error: ${d("message")}`;
|
|
120
|
+
case ProvenanceMarkErrorType.CborError:
|
|
121
|
+
return `CBOR error: ${d("message")}`;
|
|
122
|
+
case ProvenanceMarkErrorType.UrlError:
|
|
123
|
+
return `URL parsing error: ${d("message")}`;
|
|
124
|
+
case ProvenanceMarkErrorType.Base64Error:
|
|
125
|
+
return `base64 decoding error: ${d("message")}`;
|
|
126
|
+
case ProvenanceMarkErrorType.JsonError:
|
|
127
|
+
return `JSON error: ${d("message")}`;
|
|
128
|
+
case ProvenanceMarkErrorType.IntegerConversionError:
|
|
129
|
+
return `integer conversion error: ${d("message")}`;
|
|
130
|
+
case ProvenanceMarkErrorType.ValidationError:
|
|
131
|
+
return `validation error: ${d("message")}`;
|
|
132
|
+
default:
|
|
133
|
+
return type;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Result type for Provenance Mark operations.
|
|
140
|
+
*/
|
|
141
|
+
export type ProvenanceMarkResult<T> = T;
|
package/src/generator.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Ported from provenance-mark-rust/src/generator.rs
|
|
2
|
+
|
|
3
|
+
import { toBase64, fromBase64, bytesToHex } from "./utils.js";
|
|
4
|
+
import { type Cbor } from "@bcts/dcbor";
|
|
5
|
+
|
|
6
|
+
import { ProvenanceMarkError, ProvenanceMarkErrorType } from "./error.js";
|
|
7
|
+
import { type ProvenanceMarkResolution, linkLength } from "./resolution.js";
|
|
8
|
+
import { ProvenanceSeed } from "./seed.js";
|
|
9
|
+
import { RngState } from "./rng-state.js";
|
|
10
|
+
import { sha256 } from "./crypto-utils.js";
|
|
11
|
+
import { Xoshiro256StarStar } from "./xoshiro256starstar.js";
|
|
12
|
+
import { ProvenanceMark } from "./mark.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generator for creating provenance mark chains.
|
|
16
|
+
*/
|
|
17
|
+
export class ProvenanceMarkGenerator {
|
|
18
|
+
private readonly _res: ProvenanceMarkResolution;
|
|
19
|
+
private readonly _seed: ProvenanceSeed;
|
|
20
|
+
private readonly _chainId: Uint8Array;
|
|
21
|
+
private _nextSeq: number;
|
|
22
|
+
private _rngState: RngState;
|
|
23
|
+
|
|
24
|
+
private constructor(
|
|
25
|
+
res: ProvenanceMarkResolution,
|
|
26
|
+
seed: ProvenanceSeed,
|
|
27
|
+
chainId: Uint8Array,
|
|
28
|
+
nextSeq: number,
|
|
29
|
+
rngState: RngState,
|
|
30
|
+
) {
|
|
31
|
+
this._res = res;
|
|
32
|
+
this._seed = seed;
|
|
33
|
+
this._chainId = chainId;
|
|
34
|
+
this._nextSeq = nextSeq;
|
|
35
|
+
this._rngState = rngState;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
res(): ProvenanceMarkResolution {
|
|
39
|
+
return this._res;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
seed(): ProvenanceSeed {
|
|
43
|
+
return this._seed;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
chainId(): Uint8Array {
|
|
47
|
+
return new Uint8Array(this._chainId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
nextSeq(): number {
|
|
51
|
+
return this._nextSeq;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
rngState(): RngState {
|
|
55
|
+
return this._rngState;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a new generator with a seed.
|
|
60
|
+
*/
|
|
61
|
+
static newWithSeed(res: ProvenanceMarkResolution, seed: ProvenanceSeed): ProvenanceMarkGenerator {
|
|
62
|
+
// Definitely don't use the bare seed as the chain ID!
|
|
63
|
+
const digest1 = sha256(seed.toBytes());
|
|
64
|
+
const chainId = digest1.slice(0, linkLength(res));
|
|
65
|
+
const digest2 = sha256(digest1);
|
|
66
|
+
return ProvenanceMarkGenerator.new(res, seed, chainId, 0, RngState.fromBytes(digest2));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create a new generator with a passphrase.
|
|
71
|
+
*/
|
|
72
|
+
static newWithPassphrase(
|
|
73
|
+
res: ProvenanceMarkResolution,
|
|
74
|
+
passphrase: string,
|
|
75
|
+
): ProvenanceMarkGenerator {
|
|
76
|
+
const seed = ProvenanceSeed.newWithPassphrase(passphrase);
|
|
77
|
+
return ProvenanceMarkGenerator.newWithSeed(res, seed);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a new generator with custom random data.
|
|
82
|
+
*/
|
|
83
|
+
static newUsing(res: ProvenanceMarkResolution, randomData: Uint8Array): ProvenanceMarkGenerator {
|
|
84
|
+
const seed = ProvenanceSeed.newUsing(randomData);
|
|
85
|
+
return ProvenanceMarkGenerator.newWithSeed(res, seed);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create a new generator with random seed.
|
|
90
|
+
*/
|
|
91
|
+
static newRandom(res: ProvenanceMarkResolution): ProvenanceMarkGenerator {
|
|
92
|
+
const seed = ProvenanceSeed.new();
|
|
93
|
+
return ProvenanceMarkGenerator.newWithSeed(res, seed);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create a new generator with all parameters.
|
|
98
|
+
*/
|
|
99
|
+
static new(
|
|
100
|
+
res: ProvenanceMarkResolution,
|
|
101
|
+
seed: ProvenanceSeed,
|
|
102
|
+
chainId: Uint8Array,
|
|
103
|
+
nextSeq: number,
|
|
104
|
+
rngState: RngState,
|
|
105
|
+
): ProvenanceMarkGenerator {
|
|
106
|
+
const linkLen = linkLength(res);
|
|
107
|
+
if (chainId.length !== linkLen) {
|
|
108
|
+
throw new ProvenanceMarkError(ProvenanceMarkErrorType.InvalidChainIdLength, undefined, {
|
|
109
|
+
expected: linkLen,
|
|
110
|
+
actual: chainId.length,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return new ProvenanceMarkGenerator(res, seed, chainId, nextSeq, rngState);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Generate the next provenance mark in the chain.
|
|
118
|
+
*/
|
|
119
|
+
next(date: Date, info?: Cbor): ProvenanceMark {
|
|
120
|
+
const data = this._rngState.toBytes();
|
|
121
|
+
const rng = Xoshiro256StarStar.fromData(data);
|
|
122
|
+
|
|
123
|
+
const seq = this._nextSeq;
|
|
124
|
+
this._nextSeq += 1;
|
|
125
|
+
|
|
126
|
+
let key: Uint8Array;
|
|
127
|
+
if (seq === 0) {
|
|
128
|
+
key = new Uint8Array(this._chainId);
|
|
129
|
+
} else {
|
|
130
|
+
// The randomness generated by the PRNG should be portable across implementations
|
|
131
|
+
key = rng.nextBytes(linkLength(this._res));
|
|
132
|
+
this._rngState = RngState.fromBytes(rng.toData());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Clone the RNG for generating next_key
|
|
136
|
+
const nextRngData = rng.toData();
|
|
137
|
+
const nextRng = Xoshiro256StarStar.fromData(nextRngData);
|
|
138
|
+
const nextKey = nextRng.nextBytes(linkLength(this._res));
|
|
139
|
+
|
|
140
|
+
return ProvenanceMark.new(
|
|
141
|
+
this._res,
|
|
142
|
+
key,
|
|
143
|
+
nextKey,
|
|
144
|
+
new Uint8Array(this._chainId),
|
|
145
|
+
seq,
|
|
146
|
+
date,
|
|
147
|
+
info,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* String representation.
|
|
153
|
+
*/
|
|
154
|
+
toString(): string {
|
|
155
|
+
return `ProvenanceMarkGenerator(chainID: ${bytesToHex(this._chainId)}, res: ${this._res}, seed: ${this._seed.hex()}, nextSeq: ${this._nextSeq})`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* JSON serialization.
|
|
160
|
+
*/
|
|
161
|
+
toJSON(): Record<string, unknown> {
|
|
162
|
+
return {
|
|
163
|
+
res: this._res,
|
|
164
|
+
seed: toBase64(this._seed.toBytes()),
|
|
165
|
+
chainID: toBase64(this._chainId),
|
|
166
|
+
nextSeq: this._nextSeq,
|
|
167
|
+
rngState: toBase64(this._rngState.toBytes()),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Create from JSON object.
|
|
173
|
+
*/
|
|
174
|
+
static fromJSON(json: Record<string, unknown>): ProvenanceMarkGenerator {
|
|
175
|
+
const res = json["res"] as ProvenanceMarkResolution;
|
|
176
|
+
const seed = ProvenanceSeed.fromBytes(fromBase64(json["seed"] as string));
|
|
177
|
+
const chainId = fromBase64(json["chainID"] as string);
|
|
178
|
+
const nextSeq = json["nextSeq"] as number;
|
|
179
|
+
const rngState = RngState.fromBytes(fromBase64(json["rngState"] as string));
|
|
180
|
+
return ProvenanceMarkGenerator.new(res, seed, chainId, nextSeq, rngState);
|
|
181
|
+
}
|
|
182
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Ported from provenance-mark-rust
|
|
2
|
+
|
|
3
|
+
// Error types
|
|
4
|
+
export {
|
|
5
|
+
ProvenanceMarkError,
|
|
6
|
+
ProvenanceMarkErrorType,
|
|
7
|
+
type ProvenanceMarkResult,
|
|
8
|
+
} from "./error.js";
|
|
9
|
+
|
|
10
|
+
// Resolution types and utilities
|
|
11
|
+
export {
|
|
12
|
+
ProvenanceMarkResolution,
|
|
13
|
+
resolutionToNumber,
|
|
14
|
+
resolutionFromNumber,
|
|
15
|
+
linkLength,
|
|
16
|
+
seqBytesLength,
|
|
17
|
+
dateBytesLength,
|
|
18
|
+
fixedLength,
|
|
19
|
+
keyRange,
|
|
20
|
+
chainIdRange,
|
|
21
|
+
hashRange,
|
|
22
|
+
seqBytesRange,
|
|
23
|
+
dateBytesRange,
|
|
24
|
+
infoRangeStart,
|
|
25
|
+
serializeDate,
|
|
26
|
+
deserializeDate,
|
|
27
|
+
serializeSeq,
|
|
28
|
+
deserializeSeq,
|
|
29
|
+
resolutionToString,
|
|
30
|
+
resolutionToCbor,
|
|
31
|
+
resolutionFromCbor,
|
|
32
|
+
} from "./resolution.js";
|
|
33
|
+
|
|
34
|
+
// Date utilities
|
|
35
|
+
export {
|
|
36
|
+
type SerializableDate,
|
|
37
|
+
serialize2Bytes,
|
|
38
|
+
deserialize2Bytes,
|
|
39
|
+
serialize4Bytes,
|
|
40
|
+
deserialize4Bytes,
|
|
41
|
+
serialize6Bytes,
|
|
42
|
+
deserialize6Bytes,
|
|
43
|
+
rangeOfDaysInMonth,
|
|
44
|
+
dateToIso8601,
|
|
45
|
+
dateFromIso8601,
|
|
46
|
+
dateToDateString,
|
|
47
|
+
} from "./date.js";
|
|
48
|
+
|
|
49
|
+
// Crypto utilities
|
|
50
|
+
export {
|
|
51
|
+
SHA256_SIZE,
|
|
52
|
+
sha256,
|
|
53
|
+
sha256Prefix,
|
|
54
|
+
extendKey,
|
|
55
|
+
hkdfHmacSha256,
|
|
56
|
+
obfuscate,
|
|
57
|
+
} from "./crypto-utils.js";
|
|
58
|
+
|
|
59
|
+
// PRNG
|
|
60
|
+
export { Xoshiro256StarStar } from "./xoshiro256starstar.js";
|
|
61
|
+
|
|
62
|
+
// RNG State
|
|
63
|
+
export { RngState, RNG_STATE_LENGTH } from "./rng-state.js";
|
|
64
|
+
|
|
65
|
+
// Seed
|
|
66
|
+
export { ProvenanceSeed, PROVENANCE_SEED_LENGTH } from "./seed.js";
|
|
67
|
+
|
|
68
|
+
// Mark
|
|
69
|
+
export { ProvenanceMark } from "./mark.js";
|
|
70
|
+
|
|
71
|
+
// Generator
|
|
72
|
+
export { ProvenanceMarkGenerator } from "./generator.js";
|
|
73
|
+
|
|
74
|
+
// Validation
|
|
75
|
+
export {
|
|
76
|
+
ValidationReportFormat,
|
|
77
|
+
type ValidationIssue,
|
|
78
|
+
type FlaggedMark,
|
|
79
|
+
type SequenceReport,
|
|
80
|
+
type ChainReport,
|
|
81
|
+
type ValidationReport,
|
|
82
|
+
formatValidationIssue,
|
|
83
|
+
chainIdHex,
|
|
84
|
+
hasIssues,
|
|
85
|
+
formatReport,
|
|
86
|
+
validate,
|
|
87
|
+
} from "./validate.js";
|
|
88
|
+
|
|
89
|
+
// Mark Info
|
|
90
|
+
export { ProvenanceMarkInfo } from "./mark-info.js";
|
package/src/mark-info.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Ported from provenance-mark-rust/src/mark_info.rs
|
|
2
|
+
|
|
3
|
+
import { UR } from "@bcts/uniform-resources";
|
|
4
|
+
import { decodeCbor, cborData } from "@bcts/dcbor";
|
|
5
|
+
import { PROVENANCE_MARK } from "@bcts/tags";
|
|
6
|
+
|
|
7
|
+
import { ProvenanceMark } from "./mark.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Wrapper for a provenance mark with additional display information.
|
|
11
|
+
*/
|
|
12
|
+
export class ProvenanceMarkInfo {
|
|
13
|
+
private readonly _mark: ProvenanceMark;
|
|
14
|
+
private readonly _ur: UR;
|
|
15
|
+
private readonly _bytewords: string;
|
|
16
|
+
private readonly _bytemoji: string;
|
|
17
|
+
private readonly _comment: string;
|
|
18
|
+
|
|
19
|
+
private constructor(
|
|
20
|
+
mark: ProvenanceMark,
|
|
21
|
+
ur: UR,
|
|
22
|
+
bytewords: string,
|
|
23
|
+
bytemoji: string,
|
|
24
|
+
comment: string,
|
|
25
|
+
) {
|
|
26
|
+
this._mark = mark;
|
|
27
|
+
this._ur = ur;
|
|
28
|
+
this._bytewords = bytewords;
|
|
29
|
+
this._bytemoji = bytemoji;
|
|
30
|
+
this._comment = comment;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a new ProvenanceMarkInfo from a mark.
|
|
35
|
+
*/
|
|
36
|
+
static new(mark: ProvenanceMark, comment = ""): ProvenanceMarkInfo {
|
|
37
|
+
const tagName = PROVENANCE_MARK.name;
|
|
38
|
+
if (tagName === undefined) {
|
|
39
|
+
throw new Error("PROVENANCE_MARK tag has no name");
|
|
40
|
+
}
|
|
41
|
+
const cborValue = decodeCbor(mark.toCborData());
|
|
42
|
+
const ur = UR.new(tagName, cborValue);
|
|
43
|
+
const bytewords = mark.bytewordsIdentifier(true);
|
|
44
|
+
const bytemoji = mark.bytemojiIdentifier(true);
|
|
45
|
+
return new ProvenanceMarkInfo(mark, ur, bytewords, bytemoji, comment);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
mark(): ProvenanceMark {
|
|
49
|
+
return this._mark;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
ur(): UR {
|
|
53
|
+
return this._ur;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
bytewords(): string {
|
|
57
|
+
return this._bytewords;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
bytemoji(): string {
|
|
61
|
+
return this._bytemoji;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
comment(): string {
|
|
65
|
+
return this._comment;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generate a markdown summary of the mark.
|
|
70
|
+
*/
|
|
71
|
+
markdownSummary(): string {
|
|
72
|
+
const lines: string[] = [];
|
|
73
|
+
|
|
74
|
+
lines.push("---");
|
|
75
|
+
|
|
76
|
+
lines.push("");
|
|
77
|
+
lines.push(this._mark.date().toISOString());
|
|
78
|
+
|
|
79
|
+
lines.push("");
|
|
80
|
+
lines.push(`#### ${this._ur.toString()}`);
|
|
81
|
+
|
|
82
|
+
lines.push("");
|
|
83
|
+
lines.push(`#### \`${this._bytewords}\``);
|
|
84
|
+
|
|
85
|
+
lines.push("");
|
|
86
|
+
lines.push(this._bytemoji);
|
|
87
|
+
|
|
88
|
+
lines.push("");
|
|
89
|
+
if (this._comment.length > 0) {
|
|
90
|
+
lines.push(this._comment);
|
|
91
|
+
lines.push("");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* JSON serialization.
|
|
99
|
+
*/
|
|
100
|
+
toJSON(): Record<string, unknown> {
|
|
101
|
+
const result: Record<string, unknown> = {
|
|
102
|
+
ur: this._ur.toString(),
|
|
103
|
+
bytewords: this._bytewords,
|
|
104
|
+
bytemoji: this._bytemoji,
|
|
105
|
+
mark: this._mark.toJSON(),
|
|
106
|
+
};
|
|
107
|
+
if (this._comment.length > 0) {
|
|
108
|
+
result["comment"] = this._comment;
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create from JSON object.
|
|
115
|
+
*/
|
|
116
|
+
static fromJSON(json: Record<string, unknown>): ProvenanceMarkInfo {
|
|
117
|
+
const urString = json["ur"] as string;
|
|
118
|
+
const ur = UR.fromURString(urString);
|
|
119
|
+
const cborBytes = cborData(ur.cbor());
|
|
120
|
+
const mark = ProvenanceMark.fromCborData(cborBytes);
|
|
121
|
+
const bytewords = json["bytewords"] as string;
|
|
122
|
+
const bytemoji = json["bytemoji"] as string;
|
|
123
|
+
const comment = typeof json["comment"] === "string" ? json["comment"] : "";
|
|
124
|
+
return new ProvenanceMarkInfo(mark, ur, bytewords, bytemoji, comment);
|
|
125
|
+
}
|
|
126
|
+
}
|