@chainlesschain/personal-data-hub 0.3.0 → 0.3.1
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.
|
@@ -103,7 +103,84 @@ describe("Train12306Adapter", () => {
|
|
|
103
103
|
const r = assertAdapter(a);
|
|
104
104
|
expect(r.ok).toBe(true);
|
|
105
105
|
if (!r.ok) console.log(r.errors);
|
|
106
|
-
|
|
106
|
+
// v0.2: snapshot mode is the primary path (in-APK collector); legacy
|
|
107
|
+
// file-import preserved for backward compat. extractMode reports
|
|
108
|
+
// "device-pull" to match the snapshot-based social adapters family.
|
|
109
|
+
expect(a.extractMode).toBe("device-pull");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("v0.2 snapshot mode — sync yields ticket events from snapshot file", async () => {
|
|
113
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "12306-snap-"));
|
|
114
|
+
const inputPath = path.join(dir, "travel-12306.json");
|
|
115
|
+
fs.writeFileSync(inputPath, JSON.stringify({
|
|
116
|
+
schemaVersion: 1,
|
|
117
|
+
snapshottedAt: 1_700_000_000_000,
|
|
118
|
+
vendor: "12306",
|
|
119
|
+
events: [
|
|
120
|
+
{
|
|
121
|
+
kind: "ticket",
|
|
122
|
+
id: "ticket-EE123:0",
|
|
123
|
+
capturedAt: 1_700_001_000_000,
|
|
124
|
+
orderSequenceNo: "EE123",
|
|
125
|
+
ticketNumber: "T-1",
|
|
126
|
+
passengerName: "张三",
|
|
127
|
+
passengerIdLast6: "123456",
|
|
128
|
+
trainNumber: "G123",
|
|
129
|
+
fromStation: "上海虹桥",
|
|
130
|
+
toStation: "北京南",
|
|
131
|
+
departureMs: 1_700_001_000_000,
|
|
132
|
+
arrivalMs: 1_700_018_000_000,
|
|
133
|
+
seatTypeName: "二等座",
|
|
134
|
+
coachNo: "05",
|
|
135
|
+
seatNo: "12A",
|
|
136
|
+
ticketPrice: 553.5,
|
|
137
|
+
orderDateMs: 1_699_950_000_000,
|
|
138
|
+
orderTotalPrice: 553.5,
|
|
139
|
+
isCompleted: true,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
}));
|
|
143
|
+
try {
|
|
144
|
+
const a = new Train12306Adapter(); // snapshot mode — no account needed
|
|
145
|
+
const auth = await a.authenticate({ inputPath });
|
|
146
|
+
expect(auth.ok).toBe(true);
|
|
147
|
+
expect(auth.mode).toBe("snapshot-file");
|
|
148
|
+
const raws = [];
|
|
149
|
+
for await (const r of a.sync({ inputPath })) raws.push(r);
|
|
150
|
+
expect(raws).toHaveLength(1);
|
|
151
|
+
expect(raws[0].adapter).toBe("travel-12306");
|
|
152
|
+
expect(raws[0].kind).toBe("ticket");
|
|
153
|
+
expect(raws[0].originalId).toMatch(/^12306:ticket:/);
|
|
154
|
+
expect(raws[0].payload.snapshot).toBe(true);
|
|
155
|
+
const batch = a.normalize(raws[0]);
|
|
156
|
+
const v = validateBatch(batch);
|
|
157
|
+
expect(v.valid).toBe(true);
|
|
158
|
+
} finally {
|
|
159
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("v0.2 snapshot mode — rejects schemaVersion mismatch", async () => {
|
|
164
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "12306-snap-bad-"));
|
|
165
|
+
const inputPath = path.join(dir, "travel-12306.json");
|
|
166
|
+
fs.writeFileSync(inputPath, JSON.stringify({
|
|
167
|
+
schemaVersion: 99,
|
|
168
|
+
snapshottedAt: Date.now(),
|
|
169
|
+
events: [],
|
|
170
|
+
}));
|
|
171
|
+
try {
|
|
172
|
+
const a = new Train12306Adapter();
|
|
173
|
+
let threw = null;
|
|
174
|
+
try {
|
|
175
|
+
for await (const _r of a.sync({ inputPath })) { /* drain */ }
|
|
176
|
+
} catch (err) {
|
|
177
|
+
threw = err;
|
|
178
|
+
}
|
|
179
|
+
expect(threw).toBeTruthy();
|
|
180
|
+
expect(String(threw.message)).toMatch(/schemaVersion mismatch/);
|
|
181
|
+
} finally {
|
|
182
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
183
|
+
}
|
|
107
184
|
});
|
|
108
185
|
|
|
109
186
|
it("parseRecords parses JSON array", () => {
|
|
@@ -131,7 +208,7 @@ describe("Train12306Adapter", () => {
|
|
|
131
208
|
expect(recs).toHaveLength(2);
|
|
132
209
|
});
|
|
133
210
|
|
|
134
|
-
it("
|
|
211
|
+
it("legacy file-import mode — yields raw events from JSON file", async () => {
|
|
135
212
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "12306-"));
|
|
136
213
|
const dataPath = path.join(dir, "12306.json");
|
|
137
214
|
fs.writeFileSync(dataPath, JSON.stringify([
|
|
@@ -151,9 +228,24 @@ describe("Train12306Adapter", () => {
|
|
|
151
228
|
}
|
|
152
229
|
});
|
|
153
230
|
|
|
154
|
-
it("
|
|
155
|
-
|
|
156
|
-
expect(() => new Train12306Adapter({
|
|
231
|
+
it("v0.2: account.username optional at construction (snapshot mode is stateless)", () => {
|
|
232
|
+
// Previously threw — v0.2 lifts this since snapshot mode doesn't need it.
|
|
233
|
+
expect(() => new Train12306Adapter({})).not.toThrow();
|
|
234
|
+
expect(() => new Train12306Adapter()).not.toThrow();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("authenticate without inputPath OR dataPath returns NO_INPUT", async () => {
|
|
238
|
+
const a = new Train12306Adapter();
|
|
239
|
+
const r = await a.authenticate({});
|
|
240
|
+
expect(r.ok).toBe(false);
|
|
241
|
+
expect(r.reason).toBe("NO_INPUT");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("authenticate file-import mode without account.username returns NO_ACCOUNT_USERNAME", async () => {
|
|
245
|
+
const a = new Train12306Adapter({ dataPath: "/no/such/file.json" });
|
|
246
|
+
const r = await a.authenticate({});
|
|
247
|
+
expect(r.ok).toBe(false);
|
|
248
|
+
expect(r.reason).toBe("NO_ACCOUNT_USERNAME");
|
|
157
249
|
});
|
|
158
250
|
});
|
|
159
251
|
|
|
@@ -1,18 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* §2.5 v0.2 — 12306 (China Railway) ticket adapter, dual-mode.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* enrich" pattern.
|
|
11
|
-
* 2. user-uploaded JSON dump (e.g. exported from a 3rd-party 12306
|
|
12
|
-
* scraper, or hand-curated). Optional.
|
|
4
|
+
* 1. snapshot mode (opts.inputPath): in-APK Android cc reads a snapshot
|
|
5
|
+
* JSON produced by the phone's Kyfw12306LocalCollector. The collector
|
|
6
|
+
* uses captured login cookie to hit kyfw.12306.cn `/otn/queryOrder/
|
|
7
|
+
* queryMyOrder` + `/otn/queryOrder/queryMyOrderNoComplete` (cookie-only,
|
|
8
|
+
* no signing), parses each ticket into a structured event, writes JSON.
|
|
9
|
+
* Desktop-independent. account is OPTIONAL at construction.
|
|
13
10
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* 2. file-import mode (opts.dataPath, legacy v0.5): user-uploaded JSON
|
|
12
|
+
* dump from a 3rd-party 12306 scraper or hand-curated. Preserved for
|
|
13
|
+
* backward compat. account.username REQUIRED.
|
|
14
|
+
*
|
|
15
|
+
* Snapshot schema (mirrors Kyfw12306LocalCollector.SNAPSHOT_SCHEMA_VERSION):
|
|
16
|
+
*
|
|
17
|
+
* {
|
|
18
|
+
* "schemaVersion": 1,
|
|
19
|
+
* "snapshottedAt": <epoch-ms>,
|
|
20
|
+
* "vendor": "12306",
|
|
21
|
+
* "events": [
|
|
22
|
+
* { "kind": "ticket", "id": "ticket-<seqNo>:<n>", "capturedAt": <ms>,
|
|
23
|
+
* "orderSequenceNo": "...", "ticketNumber": "...",
|
|
24
|
+
* "passengerName": "张三", "passengerIdLast6": "123456",
|
|
25
|
+
* "trainNumber": "G123",
|
|
26
|
+
* "fromStation": "上海虹桥", "toStation": "北京南",
|
|
27
|
+
* "departureMs": <ms>, "arrivalMs": <ms>,
|
|
28
|
+
* "seatTypeName": "二等座", "coachNo": "05", "seatNo": "12A",
|
|
29
|
+
* "ticketPrice": 553.5, "orderDateMs": <ms>, "orderTotalPrice": 553.5,
|
|
30
|
+
* "isCompleted": true }
|
|
31
|
+
* ]
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* Sensitivity: medium — ticket history reveals travel patterns + 6 trailing
|
|
35
|
+
* digits of national ID (used for cross-source EntityResolver linking, never
|
|
36
|
+
* exposed in vault search). Snapshot file is purged after sync.
|
|
16
37
|
*/
|
|
17
38
|
|
|
18
39
|
"use strict";
|
|
@@ -21,32 +42,75 @@ const fs = require("node:fs");
|
|
|
21
42
|
const { normalizeTravelRecord, parseChineseDateTime } = require("../travel-base");
|
|
22
43
|
|
|
23
44
|
const NAME = "travel-12306";
|
|
24
|
-
const VERSION = "0.
|
|
45
|
+
const VERSION = "0.6.0";
|
|
46
|
+
const SNAPSHOT_SCHEMA_VERSION = 1;
|
|
47
|
+
|
|
48
|
+
const KIND_TICKET = "ticket";
|
|
49
|
+
const VALID_SNAPSHOT_KINDS = Object.freeze([KIND_TICKET]);
|
|
25
50
|
|
|
26
51
|
class Train12306Adapter {
|
|
27
52
|
constructor(opts = {}) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.account = opts.account;
|
|
53
|
+
// §2.5 v0.2: account.username OPTIONAL — snapshot mode is stateless and
|
|
54
|
+
// doesn't need a pre-known username. file-import mode still requires it,
|
|
55
|
+
// checked at sync time, not construction.
|
|
56
|
+
this.account = opts.account || null;
|
|
32
57
|
this._dataPath = opts.dataPath || null;
|
|
33
58
|
|
|
34
59
|
this.name = NAME;
|
|
35
60
|
this.version = VERSION;
|
|
36
|
-
this.capabilities = [
|
|
37
|
-
|
|
61
|
+
this.capabilities = [
|
|
62
|
+
"sync:snapshot",
|
|
63
|
+
"import:json",
|
|
64
|
+
"parse:12306-orders",
|
|
65
|
+
];
|
|
66
|
+
this.extractMode = "device-pull";
|
|
38
67
|
this.rateLimits = {};
|
|
39
68
|
this.dataDisclosure = {
|
|
40
69
|
fields: [
|
|
41
|
-
"12306:
|
|
70
|
+
"12306:orderSequenceNo / ticketNumber / passengerName / trainNumber / fromStation / toStation / departureMs / arrivalMs / seat / price",
|
|
42
71
|
],
|
|
43
72
|
sensitivity: "medium",
|
|
44
73
|
legalGate: false,
|
|
74
|
+
defaultInclude: {
|
|
75
|
+
ticket: true,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// _deps injection seam — vi.mock fs doesn't intercept inlined CJS require.
|
|
80
|
+
this._deps = {
|
|
81
|
+
fs,
|
|
45
82
|
};
|
|
46
83
|
}
|
|
47
84
|
|
|
48
|
-
async authenticate() {
|
|
49
|
-
|
|
85
|
+
async authenticate(ctx = {}) {
|
|
86
|
+
if (ctx && typeof ctx.inputPath === "string" && ctx.inputPath.length > 0) {
|
|
87
|
+
try {
|
|
88
|
+
this._deps.fs.accessSync(ctx.inputPath, this._deps.fs.constants.R_OK);
|
|
89
|
+
} catch (err) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
reason: "INPUT_PATH_UNREADABLE",
|
|
93
|
+
message: `snapshot not readable at ${ctx.inputPath}: ${err.message}`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { ok: true, mode: "snapshot-file" };
|
|
97
|
+
}
|
|
98
|
+
if (this._dataPath || (ctx && typeof ctx.dataPath === "string")) {
|
|
99
|
+
if (!this.account || !this.account.username) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
reason: "NO_ACCOUNT_USERNAME",
|
|
103
|
+
message: "travel-12306.authenticate: file-import mode requires account.username",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return { ok: true, account: this.account.username, mode: "file-import" };
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
reason: "NO_INPUT",
|
|
111
|
+
message:
|
|
112
|
+
"travel-12306.authenticate: needs opts.inputPath (snapshot mode) OR opts.dataPath (file-import mode)",
|
|
113
|
+
};
|
|
50
114
|
}
|
|
51
115
|
|
|
52
116
|
async healthCheck() {
|
|
@@ -54,14 +118,83 @@ class Train12306Adapter {
|
|
|
54
118
|
}
|
|
55
119
|
|
|
56
120
|
async *sync(opts = {}) {
|
|
121
|
+
if (typeof opts.inputPath === "string" && opts.inputPath.length > 0) {
|
|
122
|
+
yield* this._syncViaSnapshot(opts);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
57
125
|
const dataPath = opts.dataPath || this._dataPath;
|
|
58
|
-
if (
|
|
59
|
-
|
|
126
|
+
if (dataPath) {
|
|
127
|
+
yield* this._syncViaFileImport({ ...opts, dataPath });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
throw new Error(
|
|
131
|
+
"travel-12306.sync: needs opts.inputPath (snapshot mode, Android in-APK cc) OR opts.dataPath (file-import mode, user-uploaded JSON)",
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async *_syncViaSnapshot(opts) {
|
|
136
|
+
const raw = this._deps.fs.readFileSync(opts.inputPath, "utf-8");
|
|
137
|
+
const snapshot = JSON.parse(raw);
|
|
138
|
+
if (
|
|
139
|
+
!snapshot ||
|
|
140
|
+
typeof snapshot !== "object" ||
|
|
141
|
+
snapshot.schemaVersion !== SNAPSHOT_SCHEMA_VERSION
|
|
142
|
+
) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`travel-12306.sync: snapshot schemaVersion mismatch (got ${snapshot && snapshot.schemaVersion}, expected ${SNAPSHOT_SCHEMA_VERSION})`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const fallbackCapturedAt =
|
|
148
|
+
Number.isFinite(snapshot.snapshottedAt) && snapshot.snapshottedAt > 0
|
|
149
|
+
? Math.floor(snapshot.snapshottedAt)
|
|
150
|
+
: Date.now();
|
|
151
|
+
const include = opts.include || {};
|
|
152
|
+
const limit =
|
|
153
|
+
Number.isInteger(opts.limit) && opts.limit > 0 ? opts.limit : Infinity;
|
|
154
|
+
|
|
155
|
+
const events = Array.isArray(snapshot.events) ? snapshot.events : [];
|
|
156
|
+
let emitted = 0;
|
|
157
|
+
for (const ev of events) {
|
|
158
|
+
if (emitted >= limit) return;
|
|
159
|
+
if (!ev || typeof ev !== "object") continue;
|
|
160
|
+
const kind = ev.kind;
|
|
161
|
+
if (!VALID_SNAPSHOT_KINDS.includes(kind)) continue;
|
|
162
|
+
if (include[kind] === false) continue;
|
|
163
|
+
|
|
164
|
+
const capturedAt =
|
|
165
|
+
(Number.isFinite(ev.capturedAt) && ev.capturedAt) ||
|
|
166
|
+
(Number.isFinite(ev.departureMs) && ev.departureMs) ||
|
|
167
|
+
fallbackCapturedAt;
|
|
168
|
+
const id =
|
|
169
|
+
(typeof ev.id === "string" && ev.id.length > 0 && ev.id) ||
|
|
170
|
+
ev.orderSequenceNo ||
|
|
171
|
+
null;
|
|
172
|
+
|
|
173
|
+
yield {
|
|
174
|
+
adapter: NAME,
|
|
175
|
+
kind,
|
|
176
|
+
originalId: stableOriginalId(id || `unknown-${emitted}`),
|
|
177
|
+
capturedAt,
|
|
178
|
+
payload: { ...ev, snapshot: true },
|
|
179
|
+
};
|
|
180
|
+
emitted += 1;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async *_syncViaFileImport(opts) {
|
|
185
|
+
if (!this.account || !this.account.username) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
"travel-12306._syncViaFileImport: account.username required (set via new Train12306Adapter({ account: { username } }))",
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const dataPath = opts.dataPath;
|
|
191
|
+
if (!dataPath || !this._deps.fs.existsSync(dataPath)) return;
|
|
192
|
+
const buf = this._deps.fs.readFileSync(dataPath, "utf-8");
|
|
60
193
|
let records;
|
|
61
194
|
try {
|
|
62
195
|
records = parseRecords(buf);
|
|
63
196
|
} catch (err) {
|
|
64
|
-
throw new Error(`
|
|
197
|
+
throw new Error(`travel-12306._syncViaFileImport: parse failed: ${err.message}`);
|
|
65
198
|
}
|
|
66
199
|
for (const r of records) {
|
|
67
200
|
yield {
|
|
@@ -74,7 +207,18 @@ class Train12306Adapter {
|
|
|
74
207
|
}
|
|
75
208
|
|
|
76
209
|
normalize(raw) {
|
|
77
|
-
if (!raw || !raw.payload
|
|
210
|
+
if (!raw || !raw.payload) {
|
|
211
|
+
throw new Error("Train12306Adapter.normalize: payload missing");
|
|
212
|
+
}
|
|
213
|
+
// Snapshot-mode payload is the parsed event directly; legacy file-import
|
|
214
|
+
// payload has `.record` (already normalized shape).
|
|
215
|
+
if (raw.payload.snapshot) {
|
|
216
|
+
return normalizeTravelRecord(snapshotEventToRecord(raw.payload), {
|
|
217
|
+
adapterName: NAME,
|
|
218
|
+
adapterVersion: VERSION,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (!raw.payload.record) {
|
|
78
222
|
throw new Error("Train12306Adapter.normalize: raw.payload.record missing");
|
|
79
223
|
}
|
|
80
224
|
return normalizeTravelRecord(raw.payload.record, {
|
|
@@ -84,8 +228,43 @@ class Train12306Adapter {
|
|
|
84
228
|
}
|
|
85
229
|
}
|
|
86
230
|
|
|
231
|
+
function stableOriginalId(id) {
|
|
232
|
+
return `12306:ticket:${id}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Convert a v0.2 snapshot event into the adapter-neutral travel record
|
|
236
|
+
* shape that [normalizeTravelRecord] expects. */
|
|
237
|
+
function snapshotEventToRecord(ev) {
|
|
238
|
+
return {
|
|
239
|
+
vendorId: "12306",
|
|
240
|
+
recordId: String(ev.id || ev.orderSequenceNo || ev.ticketNumber),
|
|
241
|
+
vehicleType: "train",
|
|
242
|
+
from: { station: ev.fromStation },
|
|
243
|
+
to: { station: ev.toStation },
|
|
244
|
+
departureMs: ev.departureMs || null,
|
|
245
|
+
arrivalMs: ev.arrivalMs || null,
|
|
246
|
+
carrier: "12306",
|
|
247
|
+
vehicleNumber: ev.trainNumber,
|
|
248
|
+
totalCost:
|
|
249
|
+
Number.isFinite(ev.ticketPrice) && ev.ticketPrice > 0
|
|
250
|
+
? { value: ev.ticketPrice, currency: "CNY" }
|
|
251
|
+
: null,
|
|
252
|
+
traveler: ev.passengerName,
|
|
253
|
+
confirmationCode: ev.ticketNumber || ev.orderSequenceNo,
|
|
254
|
+
bookedAt: ev.orderDateMs || null,
|
|
255
|
+
extras: {
|
|
256
|
+
seat: ev.seatTypeName,
|
|
257
|
+
coachNo: ev.coachNo,
|
|
258
|
+
seatNumber: ev.seatNo,
|
|
259
|
+
isCompleted: ev.isCompleted,
|
|
260
|
+
idLast6: ev.passengerIdLast6 || undefined,
|
|
261
|
+
orderTotalPrice: ev.orderTotalPrice || undefined,
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
87
266
|
/**
|
|
88
|
-
* Parse a 12306 dump file. Accepts either:
|
|
267
|
+
* Parse a 12306 dump file (legacy v0.5 file-import mode). Accepts either:
|
|
89
268
|
* - JSON array of order objects
|
|
90
269
|
* - JSON object { orders: [...] }
|
|
91
270
|
* - JSONL (one order per line)
|
|
@@ -134,7 +313,7 @@ function orderToRecord(o) {
|
|
|
134
313
|
extras: {
|
|
135
314
|
seat: o.seat || o.seatType,
|
|
136
315
|
seatNumber: o.seatNumber || o.seat_number,
|
|
137
|
-
idCardLast6: o.idLast6 || undefined,
|
|
316
|
+
idCardLast6: o.idLast6 || undefined,
|
|
138
317
|
},
|
|
139
318
|
};
|
|
140
319
|
}
|
|
@@ -148,4 +327,11 @@ function numberOrParse(v) {
|
|
|
148
327
|
return null;
|
|
149
328
|
}
|
|
150
329
|
|
|
151
|
-
module.exports = {
|
|
330
|
+
module.exports = {
|
|
331
|
+
Train12306Adapter,
|
|
332
|
+
parseRecords,
|
|
333
|
+
NAME,
|
|
334
|
+
VERSION,
|
|
335
|
+
SNAPSHOT_SCHEMA_VERSION,
|
|
336
|
+
VALID_SNAPSHOT_KINDS,
|
|
337
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chainlesschain/personal-data-hub",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Personal Data Hub — UnifiedSchema + validators + KG ingest helpers for the data-back-to-the-individual middleware",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "lib/index.js",
|