plevinjs 0.2.1 → 0.2.2

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.
Files changed (63) hide show
  1. package/README.md +78 -13
  2. package/dist/cli.d.ts +4 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +16 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/compress.d.ts +5 -0
  7. package/dist/compress.d.ts.map +1 -0
  8. package/dist/compress.js +954 -0
  9. package/dist/compress.js.map +1 -0
  10. package/dist/countries.d.ts +1 -1
  11. package/dist/countries.d.ts.map +1 -1
  12. package/dist/countries.js +251 -250
  13. package/dist/countries.js.map +1 -1
  14. package/dist/derive.d.ts +1 -0
  15. package/dist/derive.d.ts.map +1 -1
  16. package/dist/derive.js +12 -6
  17. package/dist/derive.js.map +1 -1
  18. package/dist/estimate.d.ts +38 -0
  19. package/dist/estimate.d.ts.map +1 -0
  20. package/dist/estimate.js +263 -0
  21. package/dist/estimate.js.map +1 -0
  22. package/dist/extra.d.ts.map +1 -1
  23. package/dist/extra.js +12 -12
  24. package/dist/extra.js.map +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +51 -6
  27. package/dist/index.js.map +1 -1
  28. package/dist/leaves.d.ts +30 -0
  29. package/dist/leaves.d.ts.map +1 -0
  30. package/dist/leaves.js +183 -0
  31. package/dist/leaves.js.map +1 -0
  32. package/dist/lzma.d.ts +18 -0
  33. package/dist/lzma.d.ts.map +1 -1
  34. package/dist/lzma.js +18 -18
  35. package/dist/lzma.js.map +1 -1
  36. package/dist/matcher.d.ts +25 -0
  37. package/dist/matcher.d.ts.map +1 -0
  38. package/dist/matcher.js +198 -0
  39. package/dist/matcher.js.map +1 -0
  40. package/dist/models.d.ts +7 -0
  41. package/dist/models.d.ts.map +1 -1
  42. package/dist/plevin.min.js +258 -257
  43. package/dist/reader.d.ts.map +1 -1
  44. package/dist/reader.js +12 -1
  45. package/dist/reader.js.map +1 -1
  46. package/dist/selection.d.ts +34 -0
  47. package/dist/selection.d.ts.map +1 -0
  48. package/dist/selection.js +338 -0
  49. package/dist/selection.js.map +1 -0
  50. package/dist/slim.d.ts +21 -0
  51. package/dist/slim.d.ts.map +1 -0
  52. package/dist/slim.js +639 -0
  53. package/dist/slim.js.map +1 -0
  54. package/dist/slim.min.js +1 -0
  55. package/dist/stats.d.ts +61 -0
  56. package/dist/stats.d.ts.map +1 -0
  57. package/dist/stats.js +368 -0
  58. package/dist/stats.js.map +1 -0
  59. package/dist/writer.d.ts +32 -0
  60. package/dist/writer.d.ts.map +1 -0
  61. package/dist/writer.js +325 -0
  62. package/dist/writer.js.map +1 -0
  63. package/package.json +7 -2
package/dist/slim.js ADDED
@@ -0,0 +1,639 @@
1
+ /** A database cut down to the fields asked for, rebuilt the way the builder writes it. */
2
+ import { decompress } from "./lzma.js";
3
+ import { CARRIED, COLUMNS, ORDERED, parse, section, TABLES, VOCABULARIES, } from "./selection.js";
4
+ import { collect } from "./stats.js";
5
+ import { write } from "./writer.js";
6
+ export { fileName } from "./selection.js";
7
+ const MAGIC = "PLEVIN\0";
8
+ const FORMAT = 3;
9
+ const HOST_BITS = 64n;
10
+ const UNSET = -1;
11
+ const VERSIONS = [4, 6];
12
+ const SPINE_TABLES = ["place", "network", "abuse"];
13
+ const REACHED = ["network", "operator", "place", "city"];
14
+ const decoder = new TextDecoder();
15
+ const varint = (data, at) => {
16
+ let value = 0;
17
+ let shift = 1;
18
+ for (;;) {
19
+ const byte = data[at++];
20
+ value += (byte & 0x7f) * shift;
21
+ if (byte < 0x80)
22
+ return [value, at];
23
+ shift *= 128;
24
+ }
25
+ };
26
+ const bigVarint = (data, at) => {
27
+ let value = 0n;
28
+ let shift = 0n;
29
+ for (;;) {
30
+ const byte = data[at++];
31
+ value |= BigInt(byte & 0x7f) << shift;
32
+ if (byte < 0x80)
33
+ return [value, at];
34
+ shift += 7n;
35
+ }
36
+ };
37
+ const unsigned = (bytes, at, width) => {
38
+ let value = 0;
39
+ for (let step = width - 1; step >= 0; step -= 1)
40
+ value = value * 256 + bytes[at + step];
41
+ return value;
42
+ };
43
+ const twos = (value, width, signed) => signed && value >= 2 ** (width * 8 - 1) ? value - 2 ** (width * 8) : value;
44
+ const little = (bytes, at, width, signed) => {
45
+ if (width <= 4)
46
+ return twos(unsigned(bytes, at, width), width, signed);
47
+ const high = twos(unsigned(bytes, at + 4, width - 4), width - 4, signed);
48
+ return high * 2 ** 32 + unsigned(bytes, at, 4);
49
+ };
50
+ const bisect = (keys, held) => {
51
+ let low = 0;
52
+ let high = keys.length;
53
+ while (low < high) {
54
+ const middle = (low + high) >> 1;
55
+ if (keys[middle] <= held)
56
+ low = middle + 1;
57
+ else
58
+ high = middle;
59
+ }
60
+ return low;
61
+ };
62
+ const narrowGroup = (raw, at, first, size, out) => {
63
+ let value = Number(first);
64
+ out.push(value);
65
+ for (let step = 1; step < size; step += 1) {
66
+ const [gap, next] = varint(raw, at);
67
+ value += gap;
68
+ out.push(value);
69
+ at = next;
70
+ }
71
+ return at;
72
+ };
73
+ const wideGroup = (raw, at, first, size, out) => {
74
+ const networks = [first >> HOST_BITS];
75
+ for (let step = 1; step < size; step += 1) {
76
+ const [gap, next] = bigVarint(raw, at);
77
+ networks.push(networks[step - 1] + gap);
78
+ at = next;
79
+ }
80
+ for (const network of networks) {
81
+ const [host, next] = bigVarint(raw, at);
82
+ out.push((network << HOST_BITS) | host);
83
+ at = next;
84
+ }
85
+ return at;
86
+ };
87
+ const frontDecoded = (raw, held, group) => {
88
+ let cursor = 0;
89
+ for (let skip = Math.ceil(held / group) - 1; skip > 0; skip -= 1) {
90
+ cursor = varint(raw, cursor)[1];
91
+ }
92
+ const names = [];
93
+ let previous = new Uint8Array(0);
94
+ for (let at = 0; at < held; at += 1) {
95
+ if (at % group === 0)
96
+ previous = new Uint8Array(0);
97
+ const shared = raw[cursor];
98
+ const [fresh, next] = varint(raw, cursor + 1);
99
+ const name = new Uint8Array(shared + fresh);
100
+ name.set(previous.subarray(0, shared));
101
+ name.set(raw.subarray(next, next + fresh), shared);
102
+ names.push(decoder.decode(name));
103
+ previous = name;
104
+ cursor = next + fresh;
105
+ }
106
+ return names;
107
+ };
108
+ class Source {
109
+ head;
110
+ bytes;
111
+ body;
112
+ held = new Map();
113
+ constructor(bytes) {
114
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
115
+ const magic = decoder.decode(bytes.subarray(0, MAGIC.length));
116
+ if (magic !== MAGIC || bytes[MAGIC.length] !== FORMAT) {
117
+ throw new Error("not a plevin raw database");
118
+ }
119
+ const size = view.getUint32(MAGIC.length + 1, true);
120
+ const start = MAGIC.length + 5;
121
+ this.head = JSON.parse(decoder.decode(bytes.subarray(start, start + size)));
122
+ this.bytes = bytes;
123
+ this.body = start + size;
124
+ }
125
+ has(name) {
126
+ return name in this.head.sections;
127
+ }
128
+ count(name) {
129
+ return this.head.sections[name].count;
130
+ }
131
+ memo(key, make) {
132
+ if (!this.held.has(key))
133
+ this.held.set(key, make());
134
+ return this.held.get(key);
135
+ }
136
+ values(name, Type) {
137
+ return this.memo(`${name}:${Type.name}`, () => this.decodeValues(name, Type));
138
+ }
139
+ keys(name, wide) {
140
+ return this.memo(name, () => this.decodeKeys(name, wide));
141
+ }
142
+ pool() {
143
+ return this.memo("pool", () => this.decodePool());
144
+ }
145
+ openSection(name) {
146
+ const entry = this.head.sections[name];
147
+ const at = this.body + entry.offset;
148
+ const view = this.bytes.subarray(at, at + entry.bytes);
149
+ const words = new DataView(view.buffer, view.byteOffset, view.byteLength);
150
+ const blocks = words.getUint32(0, true);
151
+ const width = words.getUint32(4, true);
152
+ const offsets = Array.from({ length: blocks + 1 }, (_, at) => words.getUint32(8 + at * 4, true));
153
+ const heads = [];
154
+ for (let block = 0; block < blocks; block += 1) {
155
+ let head = 0n;
156
+ for (let step = 0; step < width; step += 1) {
157
+ head = (head << 8n) | BigInt(view[8 + 4 * (blocks + 1) + block * width + step]);
158
+ }
159
+ heads.push(head);
160
+ }
161
+ const data = view.subarray(8 + 4 * (blocks + 1) + width * blocks);
162
+ const stream = (block) => decompress(data.subarray(offsets[block], offsets[block + 1]), entry.lzma);
163
+ return { entry, blocks, heads, stream };
164
+ }
165
+ decodeValues(name, Type) {
166
+ const { entry, blocks, stream } = this.openSection(name);
167
+ const out = new Type(entry.count);
168
+ const delta = entry.encoding === "delta";
169
+ const signed = entry.encoding === "signed" || delta;
170
+ for (let block = 0; block < blocks; block += 1) {
171
+ const raw = stream(block);
172
+ const width = raw[0];
173
+ const held = Math.min(entry.block, entry.count - block * entry.block);
174
+ let running = 0;
175
+ for (let at = 0; at < held; at += 1) {
176
+ const value = little(raw, 1 + at * width, width, signed);
177
+ running = delta ? running + value : value;
178
+ out[block * entry.block + at] = running;
179
+ }
180
+ }
181
+ return out;
182
+ }
183
+ decodePool() {
184
+ const { entry, blocks, stream } = this.openSection("strings");
185
+ const out = [];
186
+ for (let block = 0; block < blocks; block += 1) {
187
+ const held = Math.min(entry.block, entry.count - block * entry.block);
188
+ out.push(...frontDecoded(stream(block), held, entry.group));
189
+ }
190
+ return out;
191
+ }
192
+ decodeKeys(name, wide) {
193
+ const { entry, blocks, heads, stream } = this.openSection(name);
194
+ const readGroup = wide ? wideGroup : narrowGroup;
195
+ const out = [];
196
+ for (let block = 0; block < blocks; block += 1) {
197
+ const raw = stream(block);
198
+ const [held, start] = varint(raw, 0);
199
+ const groups = Math.ceil(held / entry.group);
200
+ const firsts = [heads[block]];
201
+ let cursor = start;
202
+ for (let at = 1; at < groups; at += 1) {
203
+ const [gap, next] = bigVarint(raw, cursor);
204
+ firsts.push(firsts[at - 1] + gap);
205
+ cursor = next;
206
+ }
207
+ for (let at = 1; at < groups; at += 1)
208
+ cursor = varint(raw, cursor)[1];
209
+ for (let group = 0; group < groups; group += 1) {
210
+ const size = Math.min(entry.group, held - group * entry.group);
211
+ cursor = readGroup(raw, cursor, firsts[group], size, out);
212
+ }
213
+ }
214
+ return out;
215
+ }
216
+ }
217
+ const carriedColumns = (make) => Object.fromEntries(CARRIED.map((name) => [name, make(name)]));
218
+ const carries = (selection, name) => SPINE_TABLES.includes(name) ? selection.table(name) : selection.has(`spine.${name}`);
219
+ const touch = (keep, link) => {
220
+ if (link > 0 && link <= keep.length)
221
+ keep[link - 1] = 1;
222
+ };
223
+ const tally = (counts, link, weight) => {
224
+ if (link > 0)
225
+ counts[link - 1] += weight;
226
+ };
227
+ const linked = (slab, link) => {
228
+ if (link === 0)
229
+ return 0;
230
+ const at = slab.map[link - 1];
231
+ return at === undefined || at < 0 ? 0 : at + 1;
232
+ };
233
+ const columnsOf = (slab, kind) => slab.columns.flatMap((column, at) => column.kind === kind ? [[at, column.name]] : []);
234
+ const stopsOf = (source, kind, version) => {
235
+ const name = `${kind}.v${version}`;
236
+ if (!source.has(name))
237
+ return null;
238
+ return source.memo(`${name}:stops`, () => ({
239
+ keys: source.keys(name, version === 6),
240
+ columns: carriedColumns((carried) => source.values(`${name}.${carried}`, Int32Array)),
241
+ whole: source.values(`${name}.whole`, Int32Array).map((row) => row + 1),
242
+ }));
243
+ };
244
+ const hostsOf = (source, version) => source.memo(`hosts${version}`, () => {
245
+ const name = `hosts.v${version}`;
246
+ if (!source.has(name))
247
+ return { keys: [], links: new Int32Array(0) };
248
+ const links = source.values(`${name}.abuse`, Int32Array).map((row) => row + 1);
249
+ return { keys: source.keys(name, version === 6), links };
250
+ });
251
+ const networkedStops = (source, selection, stops, hosts) => {
252
+ const networked = new Uint8Array(stops.keys.length).fill(1);
253
+ if (!selection.sparse)
254
+ return networked;
255
+ const services = source.values("col.abuse.service", Float64Array);
256
+ const kept = selection.narrow.get("abuse.service");
257
+ const serving = (link) => {
258
+ const service = link > 0 ? services[link - 1] : 0;
259
+ return service > 0 && (!kept || kept.includes(service));
260
+ };
261
+ const links = selection.has("network.abuse") ? stops.columns.abuse : stops.whole;
262
+ for (let at = 0; at < networked.length; at += 1) {
263
+ networked[at] = serving(links[at]) ? 1 : 0;
264
+ }
265
+ hosts.links.forEach((link, at) => {
266
+ if (!serving(link))
267
+ return;
268
+ const spot = bisect(stops.keys, hosts.keys[at]) - 1;
269
+ if (spot >= 0)
270
+ networked[spot] = 1;
271
+ });
272
+ return networked;
273
+ };
274
+ const openFamilies = (source, selection) => VERSIONS.flatMap((version) => {
275
+ const kind = selection.has("place.lat") ? "spine" : "blocks";
276
+ const stops = stopsOf(source, kind, version);
277
+ if (!stops)
278
+ return [];
279
+ const hosts = hostsOf(source, version);
280
+ const networked = networkedStops(source, selection, stops, hosts);
281
+ return [{ version, wide: version === 6, stops, hosts, networked }];
282
+ });
283
+ const columnValues = (source, column) => source.values(section(column), Float64Array);
284
+ const makeSlab = (source, selection, name) => {
285
+ const columns = COLUMNS.filter((one) => one.table === name && selection.has(one.id));
286
+ const values = columns.map((column) => columnValues(source, column));
287
+ const size = values.length ? values[0].length : 0;
288
+ return {
289
+ name,
290
+ columns,
291
+ values,
292
+ keep: new Uint8Array(size),
293
+ map: new Int32Array(size).fill(UNSET),
294
+ rows: [],
295
+ uses: [],
296
+ };
297
+ };
298
+ const makeSlabs = (source, selection) => Object.fromEntries(TABLES.map((name) => [name, makeSlab(source, selection, name)]));
299
+ const followLinks = (slabs, slab) => {
300
+ for (const [spot, target] of columnsOf(slab, "link")) {
301
+ for (let row = 0; row < slab.keep.length; row += 1) {
302
+ if (slab.keep[row])
303
+ touch(slabs[target].keep, slab.values[spot][row]);
304
+ }
305
+ }
306
+ };
307
+ const markReached = (slabs, families) => {
308
+ touch(slabs.abuse.keep, 1);
309
+ for (const { stops, networked, hosts } of families) {
310
+ for (let spot = 0; spot < stops.keys.length; spot += 1) {
311
+ touch(slabs.place.keep, stops.columns.place[spot]);
312
+ if (networked[spot])
313
+ touch(slabs.network.keep, stops.columns.network[spot]);
314
+ touch(slabs.abuse.keep, stops.columns.abuse[spot]);
315
+ touch(slabs.abuse.keep, stops.whole[spot]);
316
+ }
317
+ for (const link of hosts.links)
318
+ touch(slabs.abuse.keep, link);
319
+ }
320
+ for (const name of REACHED)
321
+ followLinks(slabs, slabs[name]);
322
+ };
323
+ const slabValue = (selection, column, value, slabs) => {
324
+ const kept = selection.narrow.get(column.id);
325
+ const held = kept && !kept.includes(value) ? 0 : value;
326
+ return column.kind === "link" ? linked(slabs[column.name], held) : held;
327
+ };
328
+ const collapse = (slab, slabs, selection) => {
329
+ const known = new Map();
330
+ for (let at = 0; at < slab.keep.length; at += 1) {
331
+ if (!slab.keep[at])
332
+ continue;
333
+ const key = slab.columns.map((column, spot) => slabValue(selection, column, slab.values[spot][at], slabs));
334
+ const pinned = slab.name === "abuse" && at === 0;
335
+ if (!pinned && key.every((held) => held === 0))
336
+ continue;
337
+ const label = key.join(",");
338
+ let row = known.get(label);
339
+ if (row === undefined) {
340
+ row = slab.rows.length;
341
+ known.set(label, row);
342
+ slab.rows.push(key);
343
+ }
344
+ slab.map[at] = row;
345
+ }
346
+ slab.uses = new Array(slab.rows.length).fill(0);
347
+ };
348
+ const cutters = (selection, slabs, family) => {
349
+ const { stops, networked } = family;
350
+ const abuseLinks = selection.has("network.abuse") ? stops.columns.abuse : stops.whole;
351
+ const cutter = (name) => {
352
+ const column = stops.columns[name];
353
+ if (name === "place")
354
+ return (at) => linked(slabs.place, column[at]);
355
+ if (name === "network") {
356
+ return (at) => (networked[at] ? linked(slabs.network, column[at]) : 0);
357
+ }
358
+ if (name === "abuse") {
359
+ return (at) => {
360
+ const record = linked(slabs.abuse, abuseLinks[at]);
361
+ return record === 1 ? 0 : record;
362
+ };
363
+ }
364
+ return (at) => column[at];
365
+ };
366
+ return CARRIED.map((name) => (carries(selection, name) ? cutter(name) : () => 0));
367
+ };
368
+ const trim = (selection, slabs, family) => {
369
+ const { version, wide, hosts, stops } = family;
370
+ const cuts = cutters(selection, slabs, family);
371
+ const columns = carriedColumns(() => []);
372
+ const lists = CARRIED.map((name) => columns[name]);
373
+ const keys = [];
374
+ const last = new Array(cuts.length).fill(-1);
375
+ const cut = new Array(cuts.length).fill(0);
376
+ for (let at = 0; at < stops.keys.length; at += 1) {
377
+ let same = true;
378
+ for (let spot = 0; spot < cuts.length; spot += 1) {
379
+ cut[spot] = cuts[spot](at);
380
+ if (cut[spot] !== last[spot])
381
+ same = false;
382
+ }
383
+ if (same)
384
+ continue;
385
+ keys.push(stops.keys[at]);
386
+ for (let spot = 0; spot < cuts.length; spot += 1) {
387
+ lists[spot].push(cut[spot]);
388
+ last[spot] = cut[spot];
389
+ }
390
+ }
391
+ return { version, wide, hosts, keys, columns };
392
+ };
393
+ const countUses = (slabs, spines) => {
394
+ for (const { keys, columns, hosts } of spines) {
395
+ for (const name of SPINE_TABLES) {
396
+ for (let at = 0; at < keys.length; at += 1) {
397
+ tally(slabs[name].uses, columns[name][at], 1);
398
+ }
399
+ }
400
+ for (const link of hosts.links)
401
+ tally(slabs.abuse.uses, linked(slabs.abuse, link), 1);
402
+ }
403
+ for (const slab of Object.values(slabs).reverse()) {
404
+ for (const [spot, target] of columnsOf(slab, "link")) {
405
+ for (const [at, row] of slab.rows.entries()) {
406
+ tally(slabs[target].uses, row[spot], Math.max(slab.uses[at], 1));
407
+ }
408
+ }
409
+ }
410
+ };
411
+ const compareRows = (one, two) => {
412
+ for (let at = 0; at < one.length; at += 1) {
413
+ if (one[at] !== two[at])
414
+ return one[at] - two[at];
415
+ }
416
+ return 0;
417
+ };
418
+ const sortRows = (slab, order) => {
419
+ const ordered = ORDERED[slab.name];
420
+ if (ordered) {
421
+ const spots = ordered
422
+ .map((id) => slab.columns.findIndex((column) => column.id === id))
423
+ .filter((spot) => spot >= 0);
424
+ const keys = slab.rows.map((row) => [...spots.map((spot) => row[spot]), ...row]);
425
+ order.sort((one, two) => compareRows(keys[one], keys[two]));
426
+ return;
427
+ }
428
+ if (slab.name === "abuse") {
429
+ order.sort((one, two) => slab.uses[two] - slab.uses[one] || one - two);
430
+ }
431
+ };
432
+ const rank = (slab) => {
433
+ const first = slab.name === "abuse" && slab.rows.length > 0 ? 1 : 0;
434
+ const order = Array.from({ length: slab.rows.length - first }, (_, at) => at + first);
435
+ sortRows(slab, order);
436
+ const ranks = new Array(slab.rows.length).fill(0);
437
+ order.forEach((at, place) => {
438
+ ranks[at] = place + first;
439
+ });
440
+ return ranks;
441
+ };
442
+ const rankedLink = (order, link) => link === 0 ? 0 : order[link - 1] + 1;
443
+ const relink = (slab, ranks) => {
444
+ const links = columnsOf(slab, "link");
445
+ for (const row of slab.rows) {
446
+ for (const [at, target] of links)
447
+ row[at] = rankedLink(ranks[target], row[at]);
448
+ }
449
+ };
450
+ const reorder = (slab, order) => {
451
+ const moved = new Array(slab.rows.length);
452
+ slab.rows.forEach((row, at) => {
453
+ moved[order[at]] = row;
454
+ });
455
+ slab.rows = moved;
456
+ };
457
+ const rankSlabs = (slabs) => {
458
+ const ranks = {};
459
+ for (const slab of Object.values(slabs)) {
460
+ relink(slab, ranks);
461
+ const order = rank(slab);
462
+ reorder(slab, order);
463
+ ranks[slab.name] = order;
464
+ }
465
+ return ranks;
466
+ };
467
+ const relinkSpine = (spine, ranks) => {
468
+ for (const name of SPINE_TABLES) {
469
+ spine.columns[name] = spine.columns[name].map((link) => rankedLink(ranks[name], link));
470
+ }
471
+ };
472
+ const eachText = (slabs, visit) => {
473
+ for (const slab of Object.values(slabs)) {
474
+ for (const [spot] of columnsOf(slab, "text")) {
475
+ for (const row of slab.rows)
476
+ visit(row, spot);
477
+ }
478
+ }
479
+ };
480
+ const respell = (slabs, pool) => {
481
+ const used = new Uint8Array(pool.length + 1);
482
+ eachText(slabs, (row, spot) => {
483
+ used[row[spot]] = 1;
484
+ });
485
+ const renumbered = new Int32Array(pool.length + 1);
486
+ const names = pool.filter((_, at) => used[at + 1]);
487
+ let place = 0;
488
+ for (let id = 1; id < used.length; id += 1) {
489
+ if (used[id])
490
+ renumbered[id] = ++place;
491
+ }
492
+ eachText(slabs, (row, spot) => {
493
+ row[spot] = renumbered[row[spot]];
494
+ });
495
+ return names;
496
+ };
497
+ const abuseAt = (spine, address, networkAbuse) => {
498
+ const at = bisect(spine.keys, address) - 1;
499
+ const link = at < 0 ? 0 : spine.columns.abuse[at];
500
+ if (link !== 0)
501
+ return link - 1;
502
+ return networkAbuse ? null : 0;
503
+ };
504
+ const hostParts = (selection, spine, slabs, order) => {
505
+ const { hosts } = spine;
506
+ const networkAbuse = selection.has("network.abuse");
507
+ const keys = [];
508
+ const values = [];
509
+ hosts.links.forEach((link, at) => {
510
+ const held = linked(slabs.abuse, link);
511
+ if (held === 0)
512
+ return;
513
+ const position = order[held - 1];
514
+ if (abuseAt(spine, hosts.keys[at], networkAbuse) === position)
515
+ return;
516
+ keys.push(hosts.keys[at]);
517
+ values.push(position);
518
+ });
519
+ if (keys.length === 0)
520
+ return [];
521
+ const name = `hosts.v${spine.version}`;
522
+ return [
523
+ { kind: "index", name, keys, wide: spine.wide },
524
+ { kind: "values", name: `${name}.abuse`, encoding: "fixed", read: "", values },
525
+ ];
526
+ };
527
+ const spineColumnParts = (selection, spine) => CARRIED.flatMap((name) => {
528
+ const values = spine.columns[name];
529
+ const held = name === "abuse" || values.some((value) => value !== 0);
530
+ if (!carries(selection, name) || !held)
531
+ return [];
532
+ const label = `spine.v${spine.version}.${name}`;
533
+ return [{ kind: "values", name: label, encoding: "fixed", read: "", values }];
534
+ });
535
+ const spineParts = (selection, spines, slabs, ranks) => {
536
+ const parts = spines
537
+ .filter((spine) => spine.keys.length > 0)
538
+ .flatMap((spine) => [
539
+ {
540
+ kind: "index",
541
+ name: `spine.v${spine.version}`,
542
+ keys: spine.keys,
543
+ wide: spine.wide,
544
+ },
545
+ ...spineColumnParts(selection, spine),
546
+ ]);
547
+ if (!selection.table("abuse"))
548
+ return parts;
549
+ const hosts = spines.flatMap((spine) => hostParts(selection, spine, slabs, ranks.abuse));
550
+ return [...parts, ...hosts];
551
+ };
552
+ const columnParts = (slabs) => COLUMNS.flatMap((column) => {
553
+ const slab = slabs[column.table];
554
+ const at = slab.columns.findIndex((one) => one.id === column.id);
555
+ if (at < 0)
556
+ return [];
557
+ const signed = column.kind === "signed" || column.kind === "degrees";
558
+ const read = column.kind === "text" || column.kind === "degrees" ? column.kind : "";
559
+ const values = slab.rows.map((row) => row[at]);
560
+ return [
561
+ {
562
+ kind: "values",
563
+ name: section(column),
564
+ encoding: signed ? "signed" : "fixed",
565
+ read,
566
+ values,
567
+ },
568
+ ];
569
+ });
570
+ const vocabulariesFor = (source, selection) => Object.fromEntries(VOCABULARIES.filter(([name, reads]) => name in source.head.vocabularies && reads.some((id) => selection.has(id))).map(([name]) => [name, source.head.vocabularies[name]]));
571
+ const build = (source, selection, progress) => {
572
+ progress?.("reading");
573
+ const slabs = makeSlabs(source, selection);
574
+ const families = openFamilies(source, selection);
575
+ progress?.("merging");
576
+ markReached(slabs, families);
577
+ for (const slab of Object.values(slabs))
578
+ collapse(slab, slabs, selection);
579
+ const spines = families.map((family) => trim(selection, slabs, family));
580
+ countUses(slabs, spines);
581
+ const pool = respell(slabs, source.pool());
582
+ const ranks = rankSlabs(slabs);
583
+ for (const spine of spines)
584
+ relinkSpine(spine, ranks);
585
+ const parts = [
586
+ ...columnParts(slabs),
587
+ ...spineParts(selection, spines, slabs, ranks),
588
+ { kind: "strings", pool },
589
+ ];
590
+ return {
591
+ parts,
592
+ carries: SPINE_TABLES.map((name) => selection.table(name)),
593
+ fields: selection.fields,
594
+ books: vocabulariesFor(source, selection),
595
+ };
596
+ };
597
+ /** One database held open: each selection cut from it reuses what was already decoded. */
598
+ export class Slimmer {
599
+ source;
600
+ constructor(bytes) {
601
+ this.source = new Source(bytes);
602
+ }
603
+ get built() {
604
+ return this.source.head.built;
605
+ }
606
+ get fields() {
607
+ return this.source.head.fields;
608
+ }
609
+ plan(terms) {
610
+ const { fields, vocabularies } = this.source.head;
611
+ return parse(terms, new Set(fields), vocabularies);
612
+ }
613
+ /** The database for `terms` (`place+metro`, `network.asn`), as bytes to save. */
614
+ slim(terms, progress) {
615
+ const selection = this.plan(terms);
616
+ const written = build(this.source, selection, progress);
617
+ return write(written, this.built, selection.name, progress);
618
+ }
619
+ /** The counted facts `estimate` reads instead of this file, a few kilobytes of JSON. */
620
+ stats() {
621
+ const { source } = this;
622
+ const kit = {
623
+ head: source.head,
624
+ has: (name) => source.has(name),
625
+ numbers: (name) => source.values(name, Float64Array),
626
+ pool: () => source.pool(),
627
+ spine: (version, coarse) => stopsOf(source, coarse ? "blocks" : "spine", version),
628
+ hosts: (version) => hostsOf(source, version).links,
629
+ keys: (version) => ({
630
+ hosts: hostsOf(source, version).keys,
631
+ spine: source.keys(`spine.v${version}`, version === 6),
632
+ }),
633
+ };
634
+ return collect(kit);
635
+ }
636
+ }
637
+ /** A database rebuilt with only what `terms` (`place+metro`, `network.asn`) need. */
638
+ export const slim = (bytes, terms, progress) => new Slimmer(bytes).slim(terms, progress);
639
+ //# sourceMappingURL=slim.js.map