@x12i/map-store-client 0.1.3 → 0.1.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.
Files changed (62) hide show
  1. package/README.md +147 -1
  2. package/dist/estate/analytics.d.ts +41 -0
  3. package/dist/estate/analytics.d.ts.map +1 -0
  4. package/dist/estate/analytics.js +115 -0
  5. package/dist/estate/analytics.js.map +1 -0
  6. package/dist/estate/census.d.ts +115 -0
  7. package/dist/estate/census.d.ts.map +1 -0
  8. package/dist/estate/census.js +266 -0
  9. package/dist/estate/census.js.map +1 -0
  10. package/dist/estate/client.d.ts +66 -0
  11. package/dist/estate/client.d.ts.map +1 -0
  12. package/dist/estate/client.js +10 -0
  13. package/dist/estate/client.js.map +1 -0
  14. package/dist/estate/content.d.ts +77 -0
  15. package/dist/estate/content.d.ts.map +1 -0
  16. package/dist/estate/content.js +226 -0
  17. package/dist/estate/content.js.map +1 -0
  18. package/dist/estate/contract.d.ts +64 -0
  19. package/dist/estate/contract.d.ts.map +1 -0
  20. package/dist/estate/contract.js +258 -0
  21. package/dist/estate/contract.js.map +1 -0
  22. package/dist/estate/decisions.d.ts +32 -0
  23. package/dist/estate/decisions.d.ts.map +1 -0
  24. package/dist/estate/decisions.js +113 -0
  25. package/dist/estate/decisions.js.map +1 -0
  26. package/dist/estate/ids.d.ts +3 -0
  27. package/dist/estate/ids.d.ts.map +1 -0
  28. package/dist/estate/ids.js +6 -0
  29. package/dist/estate/ids.js.map +1 -0
  30. package/dist/estate/index.d.ts +26 -0
  31. package/dist/estate/index.d.ts.map +1 -0
  32. package/dist/estate/index.js +17 -0
  33. package/dist/estate/index.js.map +1 -0
  34. package/dist/estate/localStories.d.ts +28 -0
  35. package/dist/estate/localStories.d.ts.map +1 -0
  36. package/dist/estate/localStories.js +980 -0
  37. package/dist/estate/localStories.js.map +1 -0
  38. package/dist/estate/pack.d.ts +45 -0
  39. package/dist/estate/pack.d.ts.map +1 -0
  40. package/dist/estate/pack.js +83 -0
  41. package/dist/estate/pack.js.map +1 -0
  42. package/dist/estate/predictedAttack.d.ts +79 -0
  43. package/dist/estate/predictedAttack.d.ts.map +1 -0
  44. package/dist/estate/predictedAttack.js +321 -0
  45. package/dist/estate/predictedAttack.js.map +1 -0
  46. package/dist/estate/stories.d.ts +85 -0
  47. package/dist/estate/stories.d.ts.map +1 -0
  48. package/dist/estate/stories.js +215 -0
  49. package/dist/estate/stories.js.map +1 -0
  50. package/dist/fetch.d.ts +2 -0
  51. package/dist/fetch.d.ts.map +1 -1
  52. package/dist/fetch.js +13 -3
  53. package/dist/fetch.js.map +1 -1
  54. package/dist/index.d.ts +2 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +1 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/queries.d.ts +98 -0
  59. package/dist/queries.d.ts.map +1 -0
  60. package/dist/queries.js +180 -0
  61. package/dist/queries.js.map +1 -0
  62. package/package.json +14 -1
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Tier-0 census records. Three processes count published lander rows and each
3
+ * save one content record. They do not read AI products. They are not
4
+ * `estate-analytics` and they are not a bag total.
5
+ *
6
+ * One GET per board, after `working/bootstrap` has pinned the session:
7
+ * `security-map` / `tier0-analytics` / `working/{vulnerability,asset,estate}-census`.
8
+ * `data.kind` selects the record.
9
+ *
10
+ * `produced: true` and `value: 0` is a finished empty count.
11
+ * `produced: false` and `value: 0` means that source was not read.
12
+ * A missing field is the bucket `absent`. A missing KEV flag is `absent`, not `false`.
13
+ */
14
+ import { asRecord } from '../util';
15
+ import { contentData } from './content';
16
+ import { objectIdBody } from './ids';
17
+ export const EMPTY_VULNERABILITY_ROWS = [];
18
+ export const EMPTY_ASSET_ROWS = [];
19
+ export const TIER0_TARGETS = {
20
+ vulnerability: {
21
+ recordId: 'working/vulnerability-census',
22
+ kind: 'vulnerability-census',
23
+ },
24
+ asset: {
25
+ recordId: 'working/asset-census',
26
+ kind: 'asset-census',
27
+ },
28
+ estate: {
29
+ recordId: 'working/estate-census',
30
+ kind: 'estate-census',
31
+ },
32
+ };
33
+ const unproduced = () => ({ value: 0, produced: false });
34
+ /** A figure the census has not produced is absent, not zero. */
35
+ export function tier0Value(metric) {
36
+ if (!metric?.produced)
37
+ return null;
38
+ return metric.value;
39
+ }
40
+ export function publishedCount(value) {
41
+ return value == null ? '—' : value.toLocaleString();
42
+ }
43
+ /**
44
+ * List figure. A loaded census uses its own number, including a finished `0`.
45
+ * A missing row on a loaded census is unavailable.
46
+ * Before that document exists, the lander figure keeps the column from going blank.
47
+ */
48
+ export function censusOrLander(censusReady, censusRow, censusValue, landerValue) {
49
+ if (!censusReady)
50
+ return landerValue ?? null;
51
+ if (!censusRow)
52
+ return null;
53
+ return censusValue ?? null;
54
+ }
55
+ function metricOf(value) {
56
+ const rec = asRecord(value);
57
+ if (!rec ||
58
+ typeof rec.produced !== 'boolean' ||
59
+ typeof rec.value !== 'number' ||
60
+ !Number.isFinite(rec.value)) {
61
+ return unproduced();
62
+ }
63
+ return { value: rec.value, produced: rec.produced };
64
+ }
65
+ function readMetric(source, key) {
66
+ if (!source || !Object.prototype.hasOwnProperty.call(source, key))
67
+ return unproduced();
68
+ return metricOf(source[key]);
69
+ }
70
+ /** A row count. A wrapped metric counts only when that metric was produced. */
71
+ function countOf(value) {
72
+ if (typeof value === 'number' && Number.isFinite(value))
73
+ return value;
74
+ const rec = asRecord(value);
75
+ if (!rec || rec.produced !== true || typeof rec.value !== 'number' || !Number.isFinite(rec.value)) {
76
+ return null;
77
+ }
78
+ return rec.value;
79
+ }
80
+ function textOf(value) {
81
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
82
+ }
83
+ function metricMap(value) {
84
+ const rec = asRecord(value);
85
+ if (!rec)
86
+ return null;
87
+ const out = {};
88
+ for (const [key, raw] of Object.entries(rec)) {
89
+ if (!key)
90
+ continue;
91
+ out[key] = metricOf(raw);
92
+ }
93
+ return out;
94
+ }
95
+ function groupsOf(value) {
96
+ const rec = asRecord(value);
97
+ if (!rec)
98
+ return {};
99
+ const out = {};
100
+ for (const [key, raw] of Object.entries(rec)) {
101
+ const buckets = metricMap(raw);
102
+ if (buckets)
103
+ out[key] = buckets;
104
+ }
105
+ return out;
106
+ }
107
+ function vulnerabilityRows(value) {
108
+ if (!Array.isArray(value))
109
+ return [];
110
+ const rows = [];
111
+ for (const item of value) {
112
+ const rec = asRecord(item);
113
+ const id = textOf(rec?.id);
114
+ if (!rec || !id)
115
+ continue;
116
+ rows.push({
117
+ id,
118
+ cveId: textOf(rec.cveId),
119
+ label: textOf(rec.label),
120
+ affectedAssets: countOf(rec.affectedAssets),
121
+ findings: countOf(rec.findings),
122
+ });
123
+ }
124
+ return rows;
125
+ }
126
+ function assetRows(value) {
127
+ if (!Array.isArray(value))
128
+ return [];
129
+ const rows = [];
130
+ for (const item of value) {
131
+ const rec = asRecord(item);
132
+ const id = textOf(rec?.id);
133
+ if (!rec || !id)
134
+ continue;
135
+ rows.push({
136
+ id,
137
+ label: textOf(rec.label),
138
+ findings: countOf(rec.findings),
139
+ distinctCves: countOf(rec.distinctCves),
140
+ });
141
+ }
142
+ return rows;
143
+ }
144
+ /** Parse one census body. A mismatched `data.kind` is not that board's record. */
145
+ export function parseTier0Census(page, kind) {
146
+ const data = contentData(page);
147
+ if (!data || data.kind !== kind)
148
+ return null;
149
+ const totals = asRecord(data.totals);
150
+ const groups = groupsOf(data.groups);
151
+ if (kind === 'vulnerability-census') {
152
+ return {
153
+ kind,
154
+ totals: {
155
+ findings: readMetric(totals, 'findings'),
156
+ vulnerabilities: readMetric(totals, 'vulnerabilities'),
157
+ distinctCves: readMetric(totals, 'distinctCves'),
158
+ },
159
+ rows: vulnerabilityRows(data.rows),
160
+ groups,
161
+ };
162
+ }
163
+ if (kind === 'asset-census') {
164
+ return {
165
+ kind,
166
+ totals: {
167
+ assets: readMetric(totals, 'assets'),
168
+ findings: readMetric(totals, 'findings'),
169
+ },
170
+ rows: assetRows(data.rows),
171
+ groups,
172
+ };
173
+ }
174
+ const subjects = asRecord(totals?.subjects);
175
+ const bags = asRecord(totals?.bags);
176
+ return {
177
+ kind,
178
+ totals: {
179
+ subjects: {
180
+ endpointAssets: readMetric(subjects, 'endpointAssets'),
181
+ findings: readMetric(subjects, 'findings'),
182
+ alerts: readMetric(subjects, 'alerts'),
183
+ zones: readMetric(subjects, 'zones'),
184
+ devices: readMetric(subjects, 'devices'),
185
+ unresolvedSubjects: readMetric(subjects, 'unresolvedSubjects'),
186
+ },
187
+ bags: {
188
+ hypothesizedPaths: readMetric(bags, 'hypothesizedPaths'),
189
+ reachabilityClaims: readMetric(bags, 'reachabilityClaims'),
190
+ },
191
+ },
192
+ rows: [],
193
+ groups,
194
+ };
195
+ }
196
+ /** A missing record (404) or a content type the pack does not register. Any client error with `.status` qualifies. */
197
+ function isAbsent(err) {
198
+ const status = err?.status;
199
+ if (typeof status !== 'number')
200
+ return false;
201
+ if (status === 404)
202
+ return true;
203
+ const error = asRecord(asRecord(err.body)?.error);
204
+ return status === 400 && error?.code === 'ROUTE_UNKNOWN_CONTENT_TYPE';
205
+ }
206
+ /**
207
+ * One client-API read of `security-map` / `tier0-analytics`.
208
+ * A missing record or an unregistered content type yields null.
209
+ */
210
+ export async function loadTier0Census(client, board, signal) {
211
+ const target = TIER0_TARGETS[board];
212
+ try {
213
+ if (signal?.aborted)
214
+ return null;
215
+ const page = await client.getContent({
216
+ objectType: 'security-map',
217
+ contentType: 'tier0-analytics',
218
+ recordId: target.recordId,
219
+ discriminatorValue: target.kind,
220
+ });
221
+ if (signal?.aborted)
222
+ return null;
223
+ return parseTier0Census(page, target.kind);
224
+ }
225
+ catch (err) {
226
+ if (isAbsent(err))
227
+ return null;
228
+ console.warn(`tier0-analytics ${target.recordId} unavailable`, err);
229
+ return null;
230
+ }
231
+ }
232
+ /** Dialog lookup. `rows[].id` is the lander record id. */
233
+ export function censusEntryById(rows, id) {
234
+ const want = id.trim();
235
+ if (!want)
236
+ return null;
237
+ const body = objectIdBody(want);
238
+ for (const row of rows) {
239
+ if (row.id === want || row.id === body || objectIdBody(row.id) === body)
240
+ return row;
241
+ }
242
+ return null;
243
+ }
244
+ /**
245
+ * CVE dialog join. The panel's key is the CVE text. Use the lander id when that
246
+ * id is the CVE, otherwise the single census row that publishes this `cveId`.
247
+ * Several rows for one CVE stay unresolved here. Each dialog still matches by id.
248
+ */
249
+ export function vulnerabilityEntryForCve(rows, cveId) {
250
+ const direct = censusEntryById(rows, cveId);
251
+ if (direct)
252
+ return direct;
253
+ const key = cveId.trim().toUpperCase();
254
+ if (!key)
255
+ return null;
256
+ let hit = null;
257
+ for (const row of rows) {
258
+ if ((row.cveId ?? '').toUpperCase() !== key)
259
+ continue;
260
+ if (hit)
261
+ return null;
262
+ hit = row;
263
+ }
264
+ return hit;
265
+ }
266
+ //# sourceMappingURL=census.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"census.js","sourceRoot":"","sources":["../../src/estate/census.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The slice of `@x12i/memorix-client-api-client` these reads use.
3
+ * Pass the client you already build with `createMemorixReadClient`. Any object with these
4
+ * methods works, so this package does not depend on the Memorix client.
5
+ *
6
+ * The browser reaches it through the app proxy (this repo proxies `/memorix-client`).
7
+ * These reads never call Memorix itself.
8
+ */
9
+ export type ContentFilter = {
10
+ field: string;
11
+ op: 'eq';
12
+ value: string | number | boolean;
13
+ } | {
14
+ field: string;
15
+ op: 'exists';
16
+ value: true;
17
+ };
18
+ /** Arguments of `data.listContent` and `data.getContent`. */
19
+ export type ContentRead = {
20
+ objectType: string;
21
+ contentType: string;
22
+ recordId?: string;
23
+ discriminatorValue?: string;
24
+ filter?: ContentFilter[];
25
+ limit?: number;
26
+ cursor?: string;
27
+ include?: {
28
+ markdown?: boolean;
29
+ };
30
+ };
31
+ /** `data` on the Memorix read client. */
32
+ export interface EstateData {
33
+ listContent(query: ContentRead): Promise<unknown>;
34
+ getContent(query: ContentRead): Promise<unknown>;
35
+ readStory(query: {
36
+ objectType: string;
37
+ recordId: string;
38
+ template: string;
39
+ }): Promise<unknown>;
40
+ }
41
+ /** One story function from `functions(objectType)`. */
42
+ export type ObjectTypeFunction = {
43
+ id: string;
44
+ template?: string | null;
45
+ question?: string | null;
46
+ pageStory?: boolean;
47
+ };
48
+ /** Catalog and record reads on the Memorix read client. */
49
+ export interface EstateCatalog {
50
+ functions(objectType: string): Promise<ObjectTypeFunction[]>;
51
+ getContentType(objectType: string, contentType: string): Promise<unknown>;
52
+ listContentTypes(objectType: string): Promise<unknown>;
53
+ listRecords(objectType: string, query: {
54
+ filter?: {
55
+ recordId: string;
56
+ };
57
+ limit?: number;
58
+ representation?: string;
59
+ }): Promise<unknown>;
60
+ listRecordContentTypes(recordId: string): Promise<unknown>;
61
+ }
62
+ /** `analytics` on the Memorix read client. */
63
+ export interface EstateAnalyticsApi {
64
+ sourceCensus(): Promise<unknown>;
65
+ }
66
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/estate/client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,aAAa,GACrB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,GAC7D;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,CAAC;AAEjD,6DAA6D;AAC7D,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAClC,CAAC;AAEF,yCAAyC;AACzC,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,SAAS,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChG;AAED,uDAAuD;AACvD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC7D,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1E,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,WAAW,CACT,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE;QAAE,MAAM,CAAC,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAChF,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5D;AAED,8CAA8C;AAC9C,MAAM,WAAW,kBAAkB;IACjC,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAClC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The slice of `@x12i/memorix-client-api-client` these reads use.
3
+ * Pass the client you already build with `createMemorixReadClient`. Any object with these
4
+ * methods works, so this package does not depend on the Memorix client.
5
+ *
6
+ * The browser reaches it through the app proxy (this repo proxies `/memorix-client`).
7
+ * These reads never call Memorix itself.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/estate/client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Memorix content reads behind the lists.
3
+ * Business fields live on `record.data`. Narrative text lives on `_markdown.content`.
4
+ * The scope reason lives on the envelope `_metadata`, so each row carries it out.
5
+ */
6
+ import type { ContentFilter, EstateCatalog, EstateData } from './client';
7
+ export type ContentData = Record<string, unknown>;
8
+ /** A Memorix document envelope. Its facts are `data`, never the envelope itself. */
9
+ export declare function isDocumentEnvelope(value: Record<string, unknown>): boolean;
10
+ /**
11
+ * Business fields. Prefer `record.data` or `page.data`.
12
+ * An envelope without `data` is not facts. A flat body that is not an envelope still is.
13
+ */
14
+ export declare function contentData(page: unknown): ContentData | null;
15
+ /** `_markdown.content` on a record or a `{ record }` page. Empty when the head has none. */
16
+ export declare function documentMarkdown(page: unknown): string | null;
17
+ /**
18
+ * Envelope `_metadata.scopeReason`, for example `OUTSIDE_SNAPSHOT`.
19
+ * It is not a field inside `data`, so list reads lose it unless it is copied out.
20
+ */
21
+ export declare function readScopeReason(envelope: unknown): string | null;
22
+ export type ContentRow = {
23
+ recordId: string;
24
+ data: ContentData;
25
+ markdown: string | null;
26
+ /** `_metadata.scopeReason` from the envelope. Null when the record carries none. */
27
+ scopeReason: string | null;
28
+ };
29
+ export type ListContentInput = {
30
+ objectType: string;
31
+ contentType: string;
32
+ filter?: ContentFilter[];
33
+ /** Ask for `_markdown`. The analysis pages do. Table lists do not. */
34
+ includeMarkdown?: boolean;
35
+ /** Pages of `pageSize` to follow. Defaults to 20. */
36
+ maxPages?: number;
37
+ /** Defaults to 200. */
38
+ pageSize?: number;
39
+ };
40
+ /**
41
+ * Every record of one content type on one object type, following `pageInfo.nextCursor`.
42
+ * A 404, or a content type the pack does not register, is an empty list.
43
+ * Anything else is thrown.
44
+ */
45
+ export declare function listContent(client: EstateData, input: ListContentInput): Promise<ContentRow[]>;
46
+ export type ReadContentInput = {
47
+ objectType: string;
48
+ contentType: string;
49
+ recordId: string;
50
+ includeMarkdown?: boolean;
51
+ };
52
+ /**
53
+ * One record. Names the discriminator instance when the pack marks it required.
54
+ * A miss is null, not evidence that processing has not run.
55
+ */
56
+ export declare function readContent(data: EstateData, catalog: EstateCatalog, input: ReadContentInput): Promise<ContentRow | null>;
57
+ export type RecordContentInput = ReadContentInput & {
58
+ /**
59
+ * Ask for one instance, for example `{ field: 'decisionType', value: 'asset-triage' }`.
60
+ * Without one, a required discriminator is matched with `exists` so every instance comes back.
61
+ */
62
+ discriminator?: {
63
+ field: string;
64
+ value: string;
65
+ };
66
+ };
67
+ /**
68
+ * Every sibling of one content type on one record.
69
+ * `getContent` answers 404 when several records share the id, so this lists with a filter instead.
70
+ */
71
+ export declare function listRecordContent(data: EstateData, catalog: EstateCatalog, input: RecordContentInput): Promise<ContentRow[]>;
72
+ /**
73
+ * `data.displayName` on the inventory row for this subject.
74
+ * One `listContent` of `inventory` filtered by record id. Null when the row has no name.
75
+ */
76
+ export declare function inventoryDisplayName(client: EstateData, objectType: string, recordId: string): Promise<string | null>;
77
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/estate/content.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAe,aAAa,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGtF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAkBlD,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAE1E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAS7D;AAED,4FAA4F;AAC5F,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAO7D;AASD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAQhE;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oFAAoF;IACpF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AA0BF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IACzB,sEAAsE;IACtE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CA2BpG;AAWD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAuB5B;AAED,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,GAAG;IAClD;;;OAGG;IACH,aAAa,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD,CAAC;AAEF;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,UAAU,EAAE,CAAC,CAoBvB;AAID;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAexB"}
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Memorix content reads behind the lists.
3
+ * Business fields live on `record.data`. Narrative text lives on `_markdown.content`.
4
+ * The scope reason lives on the envelope `_metadata`, so each row carries it out.
5
+ */
6
+ import { requiredDiscriminator } from './pack';
7
+ function asRecord(value) {
8
+ return value && typeof value === 'object' && !Array.isArray(value)
9
+ ? value
10
+ : null;
11
+ }
12
+ function asList(value) {
13
+ return Array.isArray(value) ? value : [];
14
+ }
15
+ function text(value) {
16
+ if (typeof value === 'string')
17
+ return value.trim() || null;
18
+ if (typeof value === 'number' || typeof value === 'boolean')
19
+ return String(value);
20
+ return null;
21
+ }
22
+ /** A Memorix document envelope. Its facts are `data`, never the envelope itself. */
23
+ export function isDocumentEnvelope(value) {
24
+ return value.format === 'memorix-record/2' || '_system' in value || '_markdown' in value;
25
+ }
26
+ /**
27
+ * Business fields. Prefer `record.data` or `page.data`.
28
+ * An envelope without `data` is not facts. A flat body that is not an envelope still is.
29
+ */
30
+ export function contentData(page) {
31
+ const top = asRecord(page);
32
+ if (!top)
33
+ return null;
34
+ const record = asRecord(top.record);
35
+ const nested = asRecord(record?.data) ?? asRecord(top.data);
36
+ if (nested)
37
+ return nested;
38
+ if (record)
39
+ return isDocumentEnvelope(record) ? null : record;
40
+ if (isDocumentEnvelope(top))
41
+ return null;
42
+ return top;
43
+ }
44
+ /** `_markdown.content` on a record or a `{ record }` page. Empty when the head has none. */
45
+ export function documentMarkdown(page) {
46
+ const top = asRecord(page);
47
+ if (!top)
48
+ return null;
49
+ const record = asRecord(top.record) ?? top;
50
+ const markdown = asRecord(record._markdown);
51
+ const content = typeof markdown?.content === 'string' ? markdown.content.trim() : '';
52
+ return content || null;
53
+ }
54
+ function responseToken(value) {
55
+ return String(value ?? '')
56
+ .trim()
57
+ .toUpperCase()
58
+ .replace(/[\s-]+/g, '_');
59
+ }
60
+ /**
61
+ * Envelope `_metadata.scopeReason`, for example `OUTSIDE_SNAPSHOT`.
62
+ * It is not a field inside `data`, so list reads lose it unless it is copied out.
63
+ */
64
+ export function readScopeReason(envelope) {
65
+ const top = asRecord(envelope);
66
+ if (!top)
67
+ return null;
68
+ const from = (value) => {
69
+ const reason = responseToken(asRecord(asRecord(value)?._metadata)?.scopeReason);
70
+ return reason || null;
71
+ };
72
+ return from(top) ?? from(top.record) ?? from(top.data) ?? null;
73
+ }
74
+ function rowsFrom(page) {
75
+ const top = asRecord(page);
76
+ const list = asList(top?.records ?? top?.items ?? top?.data ?? top?.content ?? page);
77
+ return list.flatMap((raw) => {
78
+ const rec = asRecord(raw);
79
+ if (!rec)
80
+ return [];
81
+ const inner = asRecord(rec.record) ?? rec;
82
+ const data = contentData(inner);
83
+ const recordId = text(inner.recordId) ?? text(inner.id) ?? text(rec.recordId) ?? text(rec.id);
84
+ if (!data || !recordId)
85
+ return [];
86
+ return [{
87
+ recordId,
88
+ data,
89
+ markdown: documentMarkdown(inner),
90
+ scopeReason: readScopeReason(inner) ?? readScopeReason(rec),
91
+ }];
92
+ });
93
+ }
94
+ function nextCursor(page) {
95
+ const top = asRecord(page);
96
+ return text(asRecord(top?.pageInfo)?.nextCursor) ?? text(top?.nextCursor);
97
+ }
98
+ /**
99
+ * Every record of one content type on one object type, following `pageInfo.nextCursor`.
100
+ * A 404, or a content type the pack does not register, is an empty list.
101
+ * Anything else is thrown.
102
+ */
103
+ export async function listContent(client, input) {
104
+ const rows = [];
105
+ const pageSize = input.pageSize ?? 200;
106
+ const maxPages = input.maxPages ?? 20;
107
+ let cursor;
108
+ for (let page = 0; page < maxPages; page += 1) {
109
+ const query = {
110
+ objectType: input.objectType,
111
+ contentType: input.contentType,
112
+ limit: pageSize,
113
+ cursor,
114
+ filter: input.filter,
115
+ include: input.includeMarkdown ? { markdown: true } : undefined,
116
+ };
117
+ let res;
118
+ try {
119
+ res = await client.listContent(query);
120
+ }
121
+ catch (err) {
122
+ if (isUnregistered(err))
123
+ return rows;
124
+ throw err;
125
+ }
126
+ rows.push(...rowsFrom(res));
127
+ const next = nextCursor(res);
128
+ if (!next)
129
+ break;
130
+ cursor = next;
131
+ }
132
+ return rows;
133
+ }
134
+ /** A pack that does not register this content type, or a record that does not exist. */
135
+ function isUnregistered(err) {
136
+ const status = err?.status;
137
+ if (status === 404)
138
+ return true;
139
+ if (status !== 400)
140
+ return false;
141
+ const code = asRecord(asRecord(err.body)?.error)?.code;
142
+ return typeof code === 'string' && code.startsWith('ROUTE_UNKNOWN');
143
+ }
144
+ /**
145
+ * One record. Names the discriminator instance when the pack marks it required.
146
+ * A miss is null, not evidence that processing has not run.
147
+ */
148
+ export async function readContent(data, catalog, input) {
149
+ const required = await requiredDiscriminator(catalog, input.objectType, input.contentType);
150
+ let page;
151
+ try {
152
+ page = await data.getContent({
153
+ objectType: input.objectType,
154
+ contentType: input.contentType,
155
+ recordId: input.recordId,
156
+ discriminatorValue: required?.single ?? undefined,
157
+ include: input.includeMarkdown ? { markdown: true } : undefined,
158
+ });
159
+ }
160
+ catch (err) {
161
+ if (isUnregistered(err))
162
+ return null;
163
+ throw err;
164
+ }
165
+ const body = contentData(page);
166
+ if (!body)
167
+ return null;
168
+ return {
169
+ recordId: input.recordId,
170
+ data: body,
171
+ markdown: input.includeMarkdown ? documentMarkdown(page) : null,
172
+ scopeReason: readScopeReason(page),
173
+ };
174
+ }
175
+ /**
176
+ * Every sibling of one content type on one record.
177
+ * `getContent` answers 404 when several records share the id, so this lists with a filter instead.
178
+ */
179
+ export async function listRecordContent(data, catalog, input) {
180
+ if (!input.recordId)
181
+ return [];
182
+ const filter = [{ field: 'recordId', op: 'eq', value: input.recordId }];
183
+ if (input.discriminator) {
184
+ filter.push({ field: `data.${input.discriminator.field}`, op: 'eq', value: input.discriminator.value });
185
+ }
186
+ else {
187
+ const required = await requiredDiscriminator(catalog, input.objectType, input.contentType);
188
+ if (required)
189
+ filter.push({ field: required.path, op: 'exists', value: true });
190
+ }
191
+ const rows = await listContent(data, {
192
+ objectType: input.objectType,
193
+ contentType: input.contentType,
194
+ filter,
195
+ includeMarkdown: input.includeMarkdown,
196
+ maxPages: 1,
197
+ pageSize: 20,
198
+ });
199
+ if (!input.discriminator)
200
+ return rows;
201
+ const { field, value } = input.discriminator;
202
+ return rows.filter((row) => String(row.data[field] ?? '') === value);
203
+ }
204
+ const names = new Map();
205
+ /**
206
+ * `data.displayName` on the inventory row for this subject.
207
+ * One `listContent` of `inventory` filtered by record id. Null when the row has no name.
208
+ */
209
+ export function inventoryDisplayName(client, objectType, recordId) {
210
+ const key = `${objectType}\u0000${recordId}`;
211
+ const hit = names.get(key);
212
+ if (hit)
213
+ return hit;
214
+ const pending = listContent(client, {
215
+ objectType,
216
+ contentType: 'inventory',
217
+ filter: [{ field: 'recordId', op: 'eq', value: recordId }],
218
+ maxPages: 1,
219
+ pageSize: 2,
220
+ })
221
+ .then((rows) => text(rows.find((row) => row.recordId === recordId)?.data.displayName))
222
+ .catch(() => null);
223
+ names.set(key, pending);
224
+ return pending;
225
+ }
226
+ //# sourceMappingURL=content.js.map