@zixcel/projection-contracts 0.10.2 → 0.10.4
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 +1 -1
- package/package.json +16 -10
- package/src/canonical.d.mts +1 -0
- package/src/client.d.mts +2 -11
- package/src/client.mjs +16 -3
- package/src/contract.d.mts +28 -0
- package/src/contract.mjs +177 -55
- package/src/hash-browser.d.mts +1 -0
- package/src/hash-browser.mjs +11 -5
- package/src/hash-node.d.mts +1 -0
- package/src/hash-node.mjs +8 -2
- package/src/index.d.mts +5 -26
- package/src/index.mjs +20 -3
- package/src/produce.d.mts +3 -0
- package/src/producer.d.mts +4 -0
- package/src/producer.mjs +283 -166
- package/src/publication.d.mts +15 -0
- package/src/publication.mjs +58 -32
- package/src/recovery.d.mts +16 -0
- package/src/scene-actions.d.mts +2 -0
- package/src/scene-bindings.d.mts +6 -0
- package/src/scene.d.mts +4 -0
- package/src/seal.d.mts +6 -0
- package/src/snapshot.d.mts +2 -0
- package/src/source-items.d.mts +4 -0
- package/src/types/input.d.mts +54 -15
- package/src/types/publication.d.mts +16 -0
- package/src/types/snapshot.d.mts +102 -28
package/src/producer.mjs
CHANGED
|
@@ -1,180 +1,297 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
// Generated from canonical TypeScript; do not edit.
|
|
2
|
+
|
|
3
|
+
// implementation/produce.mts
|
|
4
|
+
import { canonical as canonical2, fail as fail2, list, request, shape as shape2, sourceKey, sourceRef, unique } from "./contract.mjs";
|
|
5
|
+
|
|
6
|
+
// implementation/source-items.mts
|
|
7
|
+
import { choice, copy, fail, json, refs, shape, text } from "./contract.mjs";
|
|
8
|
+
function item(v, source) {
|
|
9
|
+
shape(v, ["id", "semanticRef", "group", "value", "evidence", "provenance", "resolutionRefs", "visibility"]);
|
|
10
|
+
const id = text(v.id), semanticRef = v.semanticRef === null ? null : text(v.semanticRef);
|
|
11
|
+
shape(v.group, ["kind", "ref"]);
|
|
12
|
+
const group = { kind: choice(v.group.kind, ["semantic", "structural", "presentation"]), ref: text(v.group.ref) };
|
|
13
|
+
if (group.kind === "semantic" && group.ref !== semanticRef) fail("InvalidProjection");
|
|
14
|
+
const visibility = choice(v.visibility, ["visible", "redacted", "omitted"]);
|
|
15
|
+
const evidence = refs(v.evidence), provenance = refs(v.provenance), resolutionRefs = refs(v.resolutionRefs);
|
|
16
|
+
if (visibility === "omitted") return null;
|
|
17
|
+
if (visibility === "redacted") return { id, semanticRef: null, group: { kind: "presentation", ref: "redacted" }, value: null, evidence: [], provenance: [], resolutionRefs: [], visibility: "redacted", sourceRefs: [source] };
|
|
18
|
+
return { ...copy({ id, semanticRef, group, value: json(v.value), evidence, provenance, resolutionRefs, visibility }), sourceRefs: [source] };
|
|
14
19
|
}
|
|
15
|
-
function unresolved(v,source){
|
|
16
|
-
shape(v,[
|
|
17
|
-
return {...copy(v),sourceRefs:[source]}
|
|
20
|
+
function unresolved(v, source) {
|
|
21
|
+
shape(v, ["id", "reason", "evidence", "contextRefs", "actionRefs"]);
|
|
22
|
+
return { ...copy({ id: text(v.id), reason: text(v.reason), evidence: refs(v.evidence), contextRefs: refs(v.contextRefs), actionRefs: refs(v.actionRefs) }), sourceRefs: [source] };
|
|
18
23
|
}
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
canonical(snapshot,maximum);return frozen(snapshot)
|
|
24
|
+
function relation(v, source) {
|
|
25
|
+
shape(v, ["id", "from", "to", "relationRef"]);
|
|
26
|
+
return { ...copy({ id: text(v.id), from: text(v.from), to: text(v.to), relationRef: text(v.relationRef) }), sourceRefs: [source] };
|
|
23
27
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
|
|
29
|
+
// implementation/seal.mts
|
|
30
|
+
import { canonical, digest, frozen } from "./contract.mjs";
|
|
31
|
+
function seal(kind, key, lineage, data, maximum) {
|
|
32
|
+
const body = { contract: "projection/0.10.0", kind, key, lineage, data };
|
|
33
|
+
const snapshot = { ...body, revision: "pp:" + digest(body) };
|
|
34
|
+
canonical(snapshot, maximum);
|
|
35
|
+
return frozen(snapshot);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// implementation/produce.mts
|
|
39
|
+
function produce(input, exactSources) {
|
|
40
|
+
const r = request(input), l = r.limits;
|
|
41
|
+
canonical2(exactSources);
|
|
42
|
+
const values = list(exactSources, l.sources);
|
|
43
|
+
if (values.length !== r.sources.length) fail2("SourceUnavailable");
|
|
44
|
+
const sources = /* @__PURE__ */ new Map();
|
|
45
|
+
for (const value of values) {
|
|
46
|
+
shape2(value, ["source", "items", "relations", "unresolved", "truncated"]);
|
|
47
|
+
sourceRef(value.source);
|
|
48
|
+
if (typeof value.truncated !== "boolean") fail2("InvalidProjection");
|
|
49
|
+
const key = sourceKey(value.source);
|
|
50
|
+
if (sources.has(key)) fail2("InvalidSourceRevision");
|
|
51
|
+
sources.set(key, value);
|
|
33
52
|
}
|
|
34
|
-
const items=[],relations=[],unresolvedItems=[];
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
for(const v of list(s.
|
|
40
|
-
|
|
53
|
+
const items = [], relations = [], unresolvedItems = [];
|
|
54
|
+
let truncated = false, omitted = 0;
|
|
55
|
+
for (const ref of r.sources) {
|
|
56
|
+
const s = sources.get(sourceKey(ref));
|
|
57
|
+
if (!s) fail2("InvalidSourceRevision");
|
|
58
|
+
for (const v of list(s.items, l.items)) {
|
|
59
|
+
const next = item(v, ref);
|
|
60
|
+
if (next) items.push(next);
|
|
61
|
+
else omitted++;
|
|
41
62
|
}
|
|
42
|
-
|
|
63
|
+
for (const v of list(s.unresolved, l.unresolved)) unresolvedItems.push(unresolved(v, ref));
|
|
64
|
+
for (const v of list(s.relations, l.relations)) relations.push(relation(v, ref));
|
|
65
|
+
truncated ||= s.truncated === true;
|
|
43
66
|
}
|
|
44
|
-
list(items,l.items);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
if(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
list(items, l.items);
|
|
68
|
+
list(relations, l.relations);
|
|
69
|
+
list(unresolvedItems, l.unresolved);
|
|
70
|
+
unique([...items, ...unresolvedItems], (v) => v.id);
|
|
71
|
+
unique(relations, (v) => v.id);
|
|
72
|
+
const ids = new Set(items.filter((v) => v.visibility === "visible").map((v) => v.id));
|
|
73
|
+
for (const v of relations) if (!ids.has(v.from) || !ids.has(v.to)) fail2("InvalidProjection");
|
|
74
|
+
if (items.reduce((n, v) => n + v.provenance.length, 0) + unresolvedItems.reduce((n, v) => n + v.evidence.length, 0) > l.provenance) fail2("ProjectionLimitExceeded");
|
|
75
|
+
for (const values2 of [items, relations, unresolvedItems]) values2.sort((a, b) => a.id < b.id ? -1 : 1);
|
|
76
|
+
return seal(
|
|
77
|
+
"Data",
|
|
78
|
+
r.key,
|
|
79
|
+
{ sources: r.sources, producer: r.producer, request: r },
|
|
80
|
+
{ items, relations, unresolved: unresolvedItems, truncated, omittedByPolicy: omitted },
|
|
81
|
+
l.snapshotBytes
|
|
82
|
+
);
|
|
54
83
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if(
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
|
|
85
|
+
// implementation/scene.mts
|
|
86
|
+
import { canonical as canonical4, copy as copy4, fail as fail6, limits as limits2, list as list5, refs as refs3, roles, shape as shape6, text as text5, unique as unique5 } from "./contract.mjs";
|
|
87
|
+
|
|
88
|
+
// implementation/snapshot.mts
|
|
89
|
+
import { canonical as canonical3, digest as digest2, fail as fail3, list as list2, record as record2, request as request2, shape as shape3, sourceKey as sourceKey2, sourceRef as sourceRef2, text as text2, unique as unique2 } from "./contract.mjs";
|
|
90
|
+
function validateSnapshot(v) {
|
|
91
|
+
canonical3(v, 524288);
|
|
92
|
+
shape3(v, ["contract", "kind", "key", "lineage", "data", "revision"]);
|
|
93
|
+
if (v.contract !== "projection/0.10.0" || v.kind !== "Data" && v.kind !== "Scene") fail3("InvalidProjection");
|
|
94
|
+
const { revision, ...body } = v;
|
|
95
|
+
if (revision !== "pp:" + digest2(body)) fail3("InvalidProjection");
|
|
96
|
+
shape3(v.lineage, ["sources", "producer", "request"], v.kind === "Scene" ? ["dataProjection", "sceneConfiguration"] : []);
|
|
97
|
+
const r = request2(v.lineage.request), l = r.limits;
|
|
98
|
+
text2(v.key);
|
|
99
|
+
canonical3(v, l.snapshotBytes);
|
|
100
|
+
const declared = record2(v.lineage.request);
|
|
101
|
+
if (canonical3(v.lineage.sources) !== canonical3(declared.sources) || canonical3(v.lineage.producer) !== canonical3(declared.producer)) fail3("InvalidProjection");
|
|
102
|
+
const d = v.data;
|
|
103
|
+
shape3(d, ["items", "relations", "unresolved", "truncated", "omittedByPolicy"], v.kind === "Scene" ? ["focus", "purpose", "regions", "actions", "temporal", "presentation"] : []);
|
|
104
|
+
if (typeof d.truncated !== "boolean" || typeof d.omittedByPolicy !== "number" || !Number.isSafeInteger(d.omittedByPolicy) || d.omittedByPolicy < 0 || d.omittedByPolicy > l.sources * l.items) fail3("InvalidProjection");
|
|
105
|
+
const known = new Set(r.sources.map(sourceKey2));
|
|
106
|
+
const checkSource = (x) => {
|
|
107
|
+
const refs4 = list2(x.sourceRefs, 1);
|
|
108
|
+
if (refs4.length !== 1 || !known.has(sourceKey2(refs4[0]))) fail3("InvalidSourceRevision");
|
|
109
|
+
return sourceRef2(refs4[0]);
|
|
110
|
+
};
|
|
111
|
+
const items = list2(d.items, l.items).map((value) => {
|
|
112
|
+
const x = record2(value), source = checkSource(x), { sourceRefs, ...body2 } = x;
|
|
113
|
+
const parsed = item(body2, source);
|
|
114
|
+
if (!parsed || canonical3(parsed) !== canonical3(x)) fail3("InvalidProjection");
|
|
115
|
+
return parsed;
|
|
116
|
+
});
|
|
117
|
+
const unresolvedItems = list2(d.unresolved, l.unresolved).map((value) => {
|
|
118
|
+
const x = record2(value), source = checkSource(x), { sourceRefs, ...body2 } = x;
|
|
119
|
+
return unresolved(body2, source);
|
|
120
|
+
});
|
|
121
|
+
unique2([...items, ...unresolvedItems], (x) => x.id);
|
|
122
|
+
const relations = unique2(list2(d.relations, l.relations).map((value) => {
|
|
123
|
+
const x = record2(value), source = checkSource(x), { sourceRefs, ...body2 } = x;
|
|
124
|
+
return relation(body2, source);
|
|
125
|
+
}), (x) => x.id);
|
|
126
|
+
const ids = new Set(items.filter((x) => x.visibility === "visible").map((x) => x.id));
|
|
127
|
+
for (const x of relations) if (!ids.has(x.from) || !ids.has(x.to)) fail3("InvalidProjection");
|
|
128
|
+
if (items.reduce((n, x) => n + x.provenance.length, 0) + unresolvedItems.reduce((n, x) => n + x.evidence.length, 0) > l.provenance) fail3("ProjectionLimitExceeded");
|
|
129
|
+
const data = { items, relations, unresolved: unresolvedItems, truncated: d.truncated, omittedByPolicy: d.omittedByPolicy };
|
|
130
|
+
if (v.kind === "Data") {
|
|
131
|
+
if (v.key !== r.key) fail3("InvalidProjection");
|
|
132
|
+
} else {
|
|
133
|
+
shape3(v.lineage.dataProjection, ["key", "revision"]);
|
|
134
|
+
const base = seal("Data", r.key, { sources: r.sources, producer: r.producer, request: r }, data, l.snapshotBytes);
|
|
135
|
+
if (base.key !== v.lineage.dataProjection.key || base.revision !== v.lineage.dataProjection.revision) fail3("InvalidProjection");
|
|
136
|
+
const rebuilt = buildScene(base, v.lineage.sceneConfiguration);
|
|
137
|
+
if (canonical3(rebuilt) !== canonical3(v)) fail3("InvalidProjection");
|
|
75
138
|
}
|
|
76
|
-
|
|
77
|
-
const ids=new Set(d.items.filter(x=>x.visibility==='visible').map(x=>x.id))
|
|
78
|
-
for(const x of d.relations){shape(x,['id','from','to','relationRef','sourceRefs']);checkSource(x)
|
|
79
|
-
for(const key of ['id','from','to','relationRef'])text(x[key]);if(!ids.has(x.from)||!ids.has(x.to))fail('InvalidProjection')}
|
|
80
|
-
if(d.items.reduce((n,x)=>n+x.provenance.length,0)+d.unresolved.reduce((n,x)=>n+x.evidence.length,0)>l.provenance)fail('ProjectionLimitExceeded')
|
|
81
|
-
if(v.kind==='Data'){if(v.key!==r.key)fail('InvalidProjection')}
|
|
82
|
-
else{
|
|
83
|
-
shape(v.lineage.dataProjection,['key','revision'])
|
|
84
|
-
const base=seal('Data',r.key,{sources:r.sources,producer:r.producer,request:r},
|
|
85
|
-
{items:d.items,relations:d.relations,unresolved:d.unresolved,truncated:d.truncated,omittedByPolicy:d.omittedByPolicy},l.snapshotBytes)
|
|
86
|
-
if(base.key!==v.lineage.dataProjection.key||base.revision!==v.lineage.dataProjection.revision)fail('InvalidProjection')
|
|
87
|
-
const rebuilt=scene(base,v.lineage.sceneConfiguration)
|
|
88
|
-
if(canonical(rebuilt)!==canonical(v))fail('InvalidProjection')
|
|
89
|
-
}
|
|
90
|
-
return v
|
|
139
|
+
return v;
|
|
91
140
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if(!
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if(i.input.
|
|
141
|
+
|
|
142
|
+
// implementation/scene-actions.mts
|
|
143
|
+
import { validateInputDeclaration } from "@zixcel/interaction/input";
|
|
144
|
+
import { copy as copy2, fail as fail4, limits, list as list3, refs as refs2, shape as shape4, sourceKey as sourceKey3, sourceRef as sourceRef3, text as text3, unique as unique3 } from "./contract.mjs";
|
|
145
|
+
function actions(value, maximum, data) {
|
|
146
|
+
return unique3(list3(value, maximum).map((v) => {
|
|
147
|
+
shape4(v, ["id", "targetOwner", "commandRef", "contextRefs", "sourceRefs", "label"], ["interaction"]);
|
|
148
|
+
const id = text3(v.id), targetOwner = text3(v.targetOwner), commandRef = text3(v.commandRef), label = text3(v.label, 1024), contextRefs = refs2(v.contextRefs);
|
|
149
|
+
const expected = new Set(data.lineage.sources.map(sourceKey3));
|
|
150
|
+
const sourceRefs = list3(v.sourceRefs, limits(data.lineage.request.limits).sources).map((s) => {
|
|
151
|
+
if (!expected.has(sourceKey3(s))) fail4("InvalidSourceRevision");
|
|
152
|
+
return sourceRef3(s);
|
|
153
|
+
});
|
|
154
|
+
if (!sourceRefs.length) fail4("InvalidSourceRevision");
|
|
155
|
+
let interaction;
|
|
156
|
+
if (v.interaction !== void 0) {
|
|
157
|
+
const i = v.interaction;
|
|
158
|
+
shape4(i, ["ref", "generation", "inputContractRef", "input"]);
|
|
159
|
+
const ref = text3(i.ref), generation = text3(i.generation), inputContractRef = text3(i.inputContractRef);
|
|
160
|
+
if (validateInputDeclaration(i.input).length) fail4("InvalidProjection");
|
|
161
|
+
const input = i.input;
|
|
162
|
+
if (input.action.contract_revision !== inputContractRef || input.action.operation_id !== commandRef) fail4("InvalidProjection");
|
|
163
|
+
interaction = { ref, generation, inputContractRef, input: copy2(input) };
|
|
112
164
|
}
|
|
113
|
-
return
|
|
114
|
-
}),v=>v.id)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if(typeof value==='string'&&value.length>4096)fail('ProjectionLimitExceeded')
|
|
135
|
-
return {label:field.label,value}
|
|
136
|
-
})
|
|
137
|
-
if(binding.symbol!==undefined)text(binding.symbol,64)
|
|
138
|
-
const summary=binding.summaryPath===undefined?undefined:visibleValue(binding.itemId,binding.summaryPath)
|
|
139
|
-
if(summary!==undefined)text(summary,2048)
|
|
140
|
-
return {itemId:binding.itemId,title,...(summary===undefined?{}:{summary}),...(fields===undefined?{}:{fields}),...(binding.symbol===undefined?{}:{symbol:binding.symbol})}
|
|
141
|
-
}),item=>item.itemId)
|
|
142
|
-
let temporal
|
|
143
|
-
if(configuration.temporal!==undefined){
|
|
144
|
-
const declaration=configuration.temporal;shape(declaration,['label','items']);text(declaration.label,256)
|
|
145
|
-
const items=unique(list(declaration.items,l.items).map(binding=>{
|
|
146
|
-
shape(binding,['itemId','valuePath','format']);text(binding.itemId);list(binding.valuePath,16).forEach(key=>text(key,256))
|
|
147
|
-
if(!['utcInstant','unixMilliseconds'].includes(binding.format))fail('InvalidProjection')
|
|
148
|
-
let at=visibleValue(binding.itemId,binding.valuePath)
|
|
149
|
-
if(binding.format==='unixMilliseconds'){
|
|
150
|
-
if(!Number.isSafeInteger(at)||at<0||at>253402300799999)fail('InvalidProjection')
|
|
151
|
-
at=new Date(at).toISOString()
|
|
152
|
-
}
|
|
153
|
-
// Declared encodings only: no field-name heuristic, coercion or inferred date.
|
|
154
|
-
if(typeof at!=='string'||!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(at)||!Number.isFinite(Date.parse(at))||new Date(at).toISOString()!==at)fail('InvalidProjection')
|
|
155
|
-
return {itemId:binding.itemId,at}
|
|
156
|
-
}),item=>item.itemId).sort((a,b)=>a.at===b.at?(a.itemId<b.itemId?-1:1):(a.at<b.at?-1:1))
|
|
157
|
-
temporal={label:declaration.label,items}
|
|
158
|
-
}
|
|
159
|
-
return seal('Scene',configuration.key,lineage,{...copy(data.data),focus:configuration.focus,purpose:configuration.purpose,regions,actions,...(temporal?{temporal}:{}),...(presentation?{presentation}:{})},l.snapshotBytes)
|
|
165
|
+
return copy2({ id, targetOwner, commandRef, label, contextRefs, sourceRefs, ...interaction === void 0 ? {} : { interaction } });
|
|
166
|
+
}), (v) => v.id);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// implementation/scene-bindings.mts
|
|
170
|
+
import { fail as fail5, list as list4, shape as shape5, text as text4, unique as unique4 } from "./contract.mjs";
|
|
171
|
+
function visibleReader(data) {
|
|
172
|
+
return (itemId, valuePath) => {
|
|
173
|
+
text4(itemId);
|
|
174
|
+
const path = list4(valuePath, 16).map((key) => text4(key, 256));
|
|
175
|
+
const item2 = data.data.items.find((item3) => item3.id === itemId && item3.visibility === "visible");
|
|
176
|
+
if (!item2) fail5("InvalidProjection");
|
|
177
|
+
let value = item2.value;
|
|
178
|
+
for (const key of path) {
|
|
179
|
+
if (value === null || typeof value !== "object" || !Object.hasOwn(value, key)) fail5("InvalidProjection");
|
|
180
|
+
const d = Object.getOwnPropertyDescriptor(value, key);
|
|
181
|
+
if (!d || !("value" in d)) fail5("InvalidProjection");
|
|
182
|
+
value = d.value;
|
|
183
|
+
}
|
|
184
|
+
return value;
|
|
185
|
+
};
|
|
160
186
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if(
|
|
167
|
-
|
|
168
|
-
|
|
187
|
+
function presentation(value, maximum, visibleValue) {
|
|
188
|
+
if (value === void 0) return void 0;
|
|
189
|
+
return unique4(list4(value, maximum).map((binding) => {
|
|
190
|
+
shape5(binding, ["itemId"], ["titlePath", "title", "summaryPath", "fields", "symbol"]);
|
|
191
|
+
visibleValue(binding.itemId, []);
|
|
192
|
+
if (Object.hasOwn(binding, "title") === Object.hasOwn(binding, "titlePath")) fail5("InvalidProjection");
|
|
193
|
+
const title = text4(Object.hasOwn(binding, "titlePath") ? visibleValue(binding.itemId, binding.titlePath) : binding.title, 1024);
|
|
194
|
+
const fields = binding.fields === void 0 ? void 0 : list4(binding.fields, 16).map((field) => {
|
|
195
|
+
shape5(field, ["label", "valuePath"]);
|
|
196
|
+
const label = text4(field.label, 256);
|
|
197
|
+
const value2 = visibleValue(binding.itemId, field.valuePath);
|
|
198
|
+
if (value2 !== null && typeof value2 !== "string" && typeof value2 !== "boolean" && typeof value2 !== "number") fail5("InvalidProjection");
|
|
199
|
+
if (typeof value2 === "string" && value2.length > 4096) fail5("ProjectionLimitExceeded");
|
|
200
|
+
return { label, value: value2 };
|
|
201
|
+
});
|
|
202
|
+
const symbol = binding.symbol === void 0 ? void 0 : text4(binding.symbol, 64);
|
|
203
|
+
const summary = binding.summaryPath === void 0 ? void 0 : text4(visibleValue(binding.itemId, binding.summaryPath), 2048);
|
|
204
|
+
return { itemId: text4(binding.itemId), title, ...summary === void 0 ? {} : { summary }, ...fields === void 0 ? {} : { fields }, ...symbol === void 0 ? {} : { symbol } };
|
|
205
|
+
}), (item2) => item2.itemId);
|
|
206
|
+
}
|
|
207
|
+
function temporal(value, maximum, visibleValue) {
|
|
208
|
+
if (value === void 0) return void 0;
|
|
209
|
+
shape5(value, ["label", "items"]);
|
|
210
|
+
const label = text4(value.label, 256);
|
|
211
|
+
const items = unique4(list4(value.items, maximum).map((binding) => {
|
|
212
|
+
shape5(binding, ["itemId", "valuePath", "format"]);
|
|
213
|
+
text4(binding.itemId);
|
|
214
|
+
list4(binding.valuePath, 16).forEach((key) => text4(key, 256));
|
|
215
|
+
if (binding.format !== "utcInstant" && binding.format !== "unixMilliseconds") fail5("InvalidProjection");
|
|
216
|
+
let at = visibleValue(binding.itemId, binding.valuePath);
|
|
217
|
+
if (binding.format === "unixMilliseconds") {
|
|
218
|
+
if (typeof at !== "number" || !Number.isSafeInteger(at) || at < 0 || at > 253402300799999) fail5("InvalidProjection");
|
|
219
|
+
at = new Date(at).toISOString();
|
|
220
|
+
}
|
|
221
|
+
if (typeof at !== "string" || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(at) || !Number.isFinite(Date.parse(at)) || new Date(at).toISOString() !== at) fail5("InvalidProjection");
|
|
222
|
+
return { itemId: text4(binding.itemId), at };
|
|
223
|
+
}), (item2) => item2.itemId).sort((a, b) => a.at === b.at ? a.itemId < b.itemId ? -1 : 1 : a.at < b.at ? -1 : 1);
|
|
224
|
+
return { label, items };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// implementation/scene.mts
|
|
228
|
+
function scene(data, configuration) {
|
|
229
|
+
return buildScene(data, configuration);
|
|
230
|
+
}
|
|
231
|
+
function buildScene(value, configuration) {
|
|
232
|
+
const data = validateSnapshot(value);
|
|
233
|
+
if (data.kind !== "Data") fail6("InvalidProjection");
|
|
234
|
+
canonical4(configuration);
|
|
235
|
+
shape6(configuration, ["key", "focus", "purpose", "regions", "actions"], ["temporal", "presentation"]);
|
|
236
|
+
const key = text5(configuration.key), focus = text5(configuration.focus), purpose = text5(configuration.purpose);
|
|
237
|
+
const l = limits2(data.lineage.request.limits), ids = new Set([...data.data.items, ...data.data.unresolved].map((x) => x.id));
|
|
238
|
+
const regions = unique5(list5(configuration.regions, l.regions).map((v) => {
|
|
239
|
+
shape6(v, ["id", "role", "itemIds"]);
|
|
240
|
+
const id = text5(v.id), role = text5(v.role);
|
|
241
|
+
if (!roles.includes(role)) fail6("InvalidProjection");
|
|
242
|
+
const itemIds = unique5(refs3(v.itemIds, l.items));
|
|
243
|
+
if (itemIds.some((id2) => !ids.has(id2))) fail6("InvalidProjection");
|
|
244
|
+
return { id, role, itemIds };
|
|
245
|
+
}), (v) => v.id);
|
|
246
|
+
const actions2 = actions(configuration.actions, l.actions, data), visibleValue = visibleReader(data);
|
|
247
|
+
const presentation2 = presentation(configuration.presentation, l.items, visibleValue), temporal2 = temporal(configuration.temporal, l.items, visibleValue);
|
|
248
|
+
const lineage = { ...copy4(data.lineage), dataProjection: { key: data.key, revision: data.revision }, sceneConfiguration: copy4(configuration) };
|
|
249
|
+
return seal("Scene", key, lineage, { ...copy4(data.data), focus, purpose, regions, actions: actions2, ...temporal2 ? { temporal: temporal2 } : {}, ...presentation2 ? { presentation: presentation2 } : {} }, l.snapshotBytes);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// implementation/recovery.mts
|
|
253
|
+
import { canonical as canonical5, fail as fail7, frozen as frozen2, copy as copy5, request as request3, shape as shape7, text as text6 } from "./contract.mjs";
|
|
254
|
+
function rebuild(input, readExact) {
|
|
255
|
+
const r = request3(input);
|
|
256
|
+
const sources = r.sources.map((ref) => {
|
|
257
|
+
let value;
|
|
258
|
+
try {
|
|
259
|
+
value = readExact(ref);
|
|
260
|
+
} catch {
|
|
261
|
+
fail7("ProjectionRebuildUnavailable");
|
|
262
|
+
}
|
|
263
|
+
if (value && typeof value === "object" && "then" in value) {
|
|
264
|
+
try {
|
|
265
|
+
Promise.prototype.then.call(value, void 0, () => {
|
|
266
|
+
});
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
fail7("ProjectionRebuildUnavailable");
|
|
270
|
+
}
|
|
271
|
+
if (value == null) fail7("ProjectionRebuildUnavailable");
|
|
272
|
+
return value;
|
|
273
|
+
});
|
|
274
|
+
return produce(r, sources);
|
|
169
275
|
}
|
|
170
|
-
|
|
171
|
-
validateSnapshot(snapshot)
|
|
172
|
-
if(receiverRevision!==null)
|
|
173
|
-
if(receiverRevision===snapshot.revision)return {kind:
|
|
174
|
-
return {kind:
|
|
276
|
+
function recovery(snapshot, receiverRevision) {
|
|
277
|
+
validateSnapshot(snapshot);
|
|
278
|
+
if (receiverRevision !== null) text6(receiverRevision);
|
|
279
|
+
if (receiverRevision === snapshot.revision) return { kind: "NoChange", revision: snapshot.revision };
|
|
280
|
+
return { kind: "Snapshot", revision: snapshot.revision, payload: Buffer.from(canonical5(snapshot, 524288)) };
|
|
175
281
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
282
|
+
function adoptSnapshot(snapshot, expected) {
|
|
283
|
+
shape7(expected, ["key", "revision"]);
|
|
284
|
+
text6(expected.key);
|
|
285
|
+
text6(expected.revision);
|
|
286
|
+
validateSnapshot(snapshot);
|
|
287
|
+
if (snapshot.key !== expected.key || snapshot.revision !== expected.revision) fail7("InvalidSourceRevision");
|
|
288
|
+
return frozen2(copy5(snapshot));
|
|
180
289
|
}
|
|
290
|
+
export {
|
|
291
|
+
adoptSnapshot,
|
|
292
|
+
produce,
|
|
293
|
+
rebuild,
|
|
294
|
+
recovery,
|
|
295
|
+
scene,
|
|
296
|
+
validateSnapshot
|
|
297
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snapshot, PublicationTicket, PublicationReceipt, PendingRepair } from './types/publication.mjs';
|
|
2
|
+
import type { ProjectionRequest, ReadExact } from './types/input.mjs';
|
|
3
|
+
export declare class ProjectionSlot {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(key: string);
|
|
6
|
+
begin(): Readonly<{
|
|
7
|
+
key: string;
|
|
8
|
+
expected: `${string}-${string}-${string}-${string}-${string}`;
|
|
9
|
+
}>;
|
|
10
|
+
publish(ticket: PublicationTicket, snapshot: Snapshot): PublicationReceipt;
|
|
11
|
+
read(): Readonly<Snapshot> | null;
|
|
12
|
+
dropCache(): void;
|
|
13
|
+
}
|
|
14
|
+
export declare function pendingRepair(ticket: PublicationTicket, input: ProjectionRequest, code: PendingRepair['failure']): Readonly<PendingRepair>;
|
|
15
|
+
export declare function repair(slot: ProjectionSlot, pending: PendingRepair, readExact: ReadExact): Readonly<PublicationReceipt>;
|
package/src/publication.mjs
CHANGED
|
@@ -1,36 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
#key
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
// Generated from canonical TypeScript; do not edit.
|
|
2
|
+
|
|
3
|
+
// implementation/publication.mts
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { copy, fail, frozen, request, shape, text } from "./contract.mjs";
|
|
6
|
+
import { rebuild, validateSnapshot } from "./producer.mjs";
|
|
7
|
+
var ProjectionSlot = class {
|
|
8
|
+
#key;
|
|
9
|
+
#fence = randomUUID();
|
|
10
|
+
#snapshot = null;
|
|
11
|
+
#receipt = null;
|
|
12
|
+
constructor(key) {
|
|
13
|
+
this.#key = text(key);
|
|
14
|
+
}
|
|
15
|
+
begin() {
|
|
16
|
+
return frozen({ key: this.#key, expected: this.#fence });
|
|
17
|
+
}
|
|
18
|
+
publish(ticket, snapshot) {
|
|
19
|
+
shape(ticket, ["key", "expected"]);
|
|
20
|
+
validateSnapshot(snapshot);
|
|
21
|
+
if (ticket.key !== this.#key || snapshot.key !== this.#key) fail("InvalidProjection");
|
|
22
|
+
if (this.#receipt?.expected === ticket.expected && this.#receipt.revision === snapshot.revision) {
|
|
23
|
+
if (!this.#snapshot) this.#snapshot = frozen(copy(snapshot));
|
|
24
|
+
return this.#receipt;
|
|
17
25
|
}
|
|
18
|
-
if(ticket.expected!==this.#fence)fail(
|
|
19
|
-
this.#snapshot=frozen(copy(snapshot));
|
|
20
|
-
this.#
|
|
21
|
-
|
|
26
|
+
if (ticket.expected !== this.#fence) fail("StaleProjectionPublication");
|
|
27
|
+
this.#snapshot = frozen(copy(snapshot));
|
|
28
|
+
this.#fence = randomUUID();
|
|
29
|
+
this.#receipt = frozen({ key: this.#key, expected: ticket.expected, publication: this.#fence, revision: snapshot.revision });
|
|
30
|
+
return this.#receipt;
|
|
22
31
|
}
|
|
23
|
-
read()
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
read() {
|
|
33
|
+
if (!this.#snapshot) return null;
|
|
34
|
+
try {
|
|
35
|
+
return frozen(copy(validateSnapshot(this.#snapshot)));
|
|
36
|
+
} catch {
|
|
37
|
+
fail("ProjectionUnavailable");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
dropCache() {
|
|
41
|
+
this.#snapshot = null;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
function pendingRepair(ticket, input, code) {
|
|
45
|
+
shape(ticket, ["key", "expected"]);
|
|
46
|
+
text(ticket.key);
|
|
47
|
+
text(ticket.expected);
|
|
48
|
+
if (!["ProjectionUnavailable", "ProjectionLimitExceeded"].includes(code)) fail("InvalidProjection");
|
|
49
|
+
const r = request(input);
|
|
50
|
+
if (r.key !== ticket.key) fail("InvalidProjection");
|
|
51
|
+
return frozen({ ticket: copy(ticket), request: r, failure: code });
|
|
31
52
|
}
|
|
32
|
-
|
|
33
|
-
shape(pending,[
|
|
34
|
-
const p=pendingRepair(pending.ticket,pending.request,pending.failure)
|
|
35
|
-
return slot.publish(p.ticket,rebuild(p.request,readExact))
|
|
53
|
+
function repair(slot, pending, readExact) {
|
|
54
|
+
shape(pending, ["ticket", "request", "failure"]);
|
|
55
|
+
const p = pendingRepair(pending.ticket, pending.request, pending.failure);
|
|
56
|
+
return slot.publish(p.ticket, rebuild(p.request, readExact));
|
|
36
57
|
}
|
|
58
|
+
export {
|
|
59
|
+
ProjectionSlot,
|
|
60
|
+
pendingRepair,
|
|
61
|
+
repair
|
|
62
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ProjectionRequest, ReadExact } from './types/input.mjs';
|
|
2
|
+
import type { DataProjection } from './types/snapshot.mjs';
|
|
3
|
+
import type { Snapshot } from './types/publication.mjs';
|
|
4
|
+
export declare function rebuild(input: ProjectionRequest, readExact: ReadExact): DataProjection;
|
|
5
|
+
export declare function recovery(snapshot: Snapshot, receiverRevision: string | null): {
|
|
6
|
+
kind: 'NoChange';
|
|
7
|
+
revision: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'Snapshot';
|
|
10
|
+
revision: string;
|
|
11
|
+
payload: Uint8Array;
|
|
12
|
+
};
|
|
13
|
+
export declare function adoptSnapshot(snapshot: Snapshot, expected: {
|
|
14
|
+
key: string;
|
|
15
|
+
revision: string;
|
|
16
|
+
}): Readonly<Snapshot>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DataProjection, Json, SceneProjection } from './types/snapshot.mjs';
|
|
2
|
+
type VisibleReader = (itemId: unknown, valuePath: unknown) => Json;
|
|
3
|
+
export declare function visibleReader(data: DataProjection): VisibleReader;
|
|
4
|
+
export declare function presentation(value: unknown, maximum: number, visibleValue: VisibleReader): NonNullable<SceneProjection['data']['presentation']> | undefined;
|
|
5
|
+
export declare function temporal(value: unknown, maximum: number, visibleValue: VisibleReader): SceneProjection['data']['temporal'];
|
|
6
|
+
export {};
|