@x12i/memorix-service 3.12.3 → 3.13.0
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 +2 -2
- package/dist/activity-ui.js +1 -1
- package/dist/agent-catalog.d.ts +32 -0
- package/dist/agent-catalog.d.ts.map +1 -0
- package/dist/agent-catalog.js +80 -0
- package/dist/agent-catalog.js.map +1 -0
- package/dist/analytics/engine.d.ts.map +1 -1
- package/dist/analytics/engine.js +2 -1
- package/dist/analytics/engine.js.map +1 -1
- package/dist/analytics/state.d.ts +1 -1
- package/dist/analytics/tracker.d.ts +2 -0
- package/dist/analytics/tracker.d.ts.map +1 -1
- package/dist/analytics/tracker.js +15 -8
- package/dist/analytics/tracker.js.map +1 -1
- package/dist/anomaly/catalog.d.ts +4 -0
- package/dist/anomaly/catalog.d.ts.map +1 -0
- package/dist/anomaly/catalog.js +315 -0
- package/dist/anomaly/catalog.js.map +1 -0
- package/dist/anomaly/descriptors.d.ts +11 -0
- package/dist/anomaly/descriptors.d.ts.map +1 -0
- package/dist/anomaly/descriptors.js +113 -0
- package/dist/anomaly/descriptors.js.map +1 -0
- package/dist/anomaly/index.d.ts +6 -0
- package/dist/anomaly/index.d.ts.map +1 -0
- package/dist/anomaly/index.js +5 -0
- package/dist/anomaly/index.js.map +1 -0
- package/dist/anomaly/records.d.ts +28 -0
- package/dist/anomaly/records.d.ts.map +1 -0
- package/dist/anomaly/records.js +384 -0
- package/dist/anomaly/records.js.map +1 -0
- package/dist/anomaly/types.d.ts +116 -0
- package/dist/anomaly/types.d.ts.map +1 -0
- package/dist/anomaly/types.js +24 -0
- package/dist/anomaly/types.js.map +1 -0
- package/dist/anomaly/values.d.ts +31 -0
- package/dist/anomaly/values.d.ts.map +1 -0
- package/dist/anomaly/values.js +171 -0
- package/dist/anomaly/values.js.map +1 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +34 -1
- package/dist/app.js.map +1 -1
- package/dist/arrivals-backfill.d.ts +23 -0
- package/dist/arrivals-backfill.d.ts.map +1 -0
- package/dist/arrivals-backfill.js +132 -0
- package/dist/arrivals-backfill.js.map +1 -0
- package/dist/arrivals-routes.d.ts +8 -0
- package/dist/arrivals-routes.d.ts.map +1 -0
- package/dist/arrivals-routes.js +17 -0
- package/dist/arrivals-routes.js.map +1 -0
- package/dist/arrivals-ui.d.ts +3 -0
- package/dist/arrivals-ui.d.ts.map +1 -0
- package/dist/arrivals-ui.js +277 -0
- package/dist/arrivals-ui.js.map +1 -0
- package/dist/catalog-bridge.d.ts +18 -0
- package/dist/catalog-bridge.d.ts.map +1 -1
- package/dist/catalog-bridge.js +115 -7
- package/dist/catalog-bridge.js.map +1 -1
- package/dist/catalog-live-ui.js +1 -1
- package/dist/content-arrivals-countex.d.ts +90 -0
- package/dist/content-arrivals-countex.d.ts.map +1 -0
- package/dist/content-arrivals-countex.js +252 -0
- package/dist/content-arrivals-countex.js.map +1 -0
- package/dist/data-changes-ui.js +1 -1
- package/dist/data-changes.d.ts +3 -0
- package/dist/data-changes.d.ts.map +1 -1
- package/dist/data-changes.js +22 -21
- package/dist/data-changes.js.map +1 -1
- package/dist/errors.d.ts +6 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +22 -2
- package/dist/errors.js.map +1 -1
- package/dist/hooks-live-ui.js +1 -1
- package/dist/knowledge-upstream.d.ts +32 -0
- package/dist/knowledge-upstream.d.ts.map +1 -1
- package/dist/knowledge-upstream.js +86 -2
- package/dist/knowledge-upstream.js.map +1 -1
- package/dist/land-v2-guards.d.ts +5 -12
- package/dist/land-v2-guards.d.ts.map +1 -1
- package/dist/land-v2-guards.js +10 -33
- package/dist/land-v2-guards.js.map +1 -1
- package/dist/live-ops-nav.d.ts.map +1 -1
- package/dist/live-ops-nav.js +4 -0
- package/dist/live-ops-nav.js.map +1 -1
- package/dist/mcp/nexum-tools.d.ts +1 -1
- package/dist/mcp/nexum-tools.d.ts.map +1 -1
- package/dist/mcp/nexum-tools.js +39 -22
- package/dist/mcp/nexum-tools.js.map +1 -1
- package/dist/nexum-analysis/enqueue.d.ts.map +1 -1
- package/dist/nexum-analysis/enqueue.js +2 -1
- package/dist/nexum-analysis/enqueue.js.map +1 -1
- package/dist/nexum-analysis/knowledge-read.d.ts +45 -0
- package/dist/nexum-analysis/knowledge-read.d.ts.map +1 -0
- package/dist/nexum-analysis/knowledge-read.js +26 -0
- package/dist/nexum-analysis/knowledge-read.js.map +1 -0
- package/dist/nexum-analysis/outbox.d.ts +1 -1
- package/dist/nexum-analysis/reconcile.d.ts.map +1 -1
- package/dist/nexum-analysis/reconcile.js +3 -2
- package/dist/nexum-analysis/reconcile.js.map +1 -1
- package/dist/openapi/openapi.json +18354 -17673
- package/dist/operational-db.d.ts +2 -2
- package/dist/operational-db.d.ts.map +1 -1
- package/dist/operational-db.js +8 -11
- package/dist/operational-db.js.map +1 -1
- package/dist/patches/access.d.ts +15 -0
- package/dist/patches/access.d.ts.map +1 -0
- package/dist/patches/access.js +27 -0
- package/dist/patches/access.js.map +1 -0
- package/dist/patches/engine.d.ts +67 -0
- package/dist/patches/engine.d.ts.map +1 -0
- package/dist/patches/engine.js +966 -0
- package/dist/patches/engine.js.map +1 -0
- package/dist/patches/guards.d.ts +23 -0
- package/dist/patches/guards.d.ts.map +1 -0
- package/dist/patches/guards.js +77 -0
- package/dist/patches/guards.js.map +1 -0
- package/dist/patches/legacy-rows.d.ts +56 -0
- package/dist/patches/legacy-rows.d.ts.map +1 -0
- package/dist/patches/legacy-rows.js +162 -0
- package/dist/patches/legacy-rows.js.map +1 -0
- package/dist/patches/ops.d.ts +24 -0
- package/dist/patches/ops.d.ts.map +1 -0
- package/dist/patches/ops.js +204 -0
- package/dist/patches/ops.js.map +1 -0
- package/dist/patches/parse.d.ts +5 -0
- package/dist/patches/parse.d.ts.map +1 -0
- package/dist/patches/parse.js +323 -0
- package/dist/patches/parse.js.map +1 -0
- package/dist/patches/paths.d.ts +38 -0
- package/dist/patches/paths.d.ts.map +1 -0
- package/dist/patches/paths.js +204 -0
- package/dist/patches/paths.js.map +1 -0
- package/dist/patches/service.d.ts +16 -0
- package/dist/patches/service.d.ts.map +1 -0
- package/dist/patches/service.js +128 -0
- package/dist/patches/service.js.map +1 -0
- package/dist/patches/store.d.ts +87 -0
- package/dist/patches/store.d.ts.map +1 -0
- package/dist/patches/store.js +27 -0
- package/dist/patches/store.js.map +1 -0
- package/dist/patches/types.d.ts +112 -0
- package/dist/patches/types.d.ts.map +1 -0
- package/dist/patches/types.js +5 -0
- package/dist/patches/types.js.map +1 -0
- package/dist/platform.d.ts +17 -3
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +114 -11
- package/dist/platform.js.map +1 -1
- package/dist/routes/anomaly.d.ts +5 -0
- package/dist/routes/anomaly.d.ts.map +1 -0
- package/dist/routes/anomaly.js +308 -0
- package/dist/routes/anomaly.js.map +1 -0
- package/dist/routes/data.d.ts.map +1 -1
- package/dist/routes/data.js +252 -210
- package/dist/routes/data.js.map +1 -1
- package/dist/routes/knowledge.d.ts.map +1 -1
- package/dist/routes/knowledge.js +6 -22
- package/dist/routes/knowledge.js.map +1 -1
- package/dist/routes/memory.d.ts.map +1 -1
- package/dist/routes/memory.js +2 -4
- package/dist/routes/memory.js.map +1 -1
- package/dist/routes/metadata.d.ts.map +1 -1
- package/dist/routes/metadata.js +40 -1
- package/dist/routes/metadata.js.map +1 -1
- package/dist/routes/mgc-landing.d.ts.map +1 -1
- package/dist/routes/mgc-landing.js +64 -94
- package/dist/routes/mgc-landing.js.map +1 -1
- package/dist/routes/patches.d.ts +7 -0
- package/dist/routes/patches.d.ts.map +1 -0
- package/dist/routes/patches.js +111 -0
- package/dist/routes/patches.js.map +1 -0
- package/dist/routes/subject-roots-backfill.d.ts +4 -0
- package/dist/routes/subject-roots-backfill.d.ts.map +1 -0
- package/dist/routes/subject-roots-backfill.js +15 -0
- package/dist/routes/subject-roots-backfill.js.map +1 -0
- package/dist/routes/workflow.d.ts.map +1 -1
- package/dist/routes/workflow.js +4 -10
- package/dist/routes/workflow.js.map +1 -1
- package/dist/single-read-discriminator.d.ts +17 -0
- package/dist/single-read-discriminator.d.ts.map +1 -0
- package/dist/single-read-discriminator.js +67 -0
- package/dist/single-read-discriminator.js.map +1 -0
- package/dist/subject-roots-backfill.d.ts +24 -0
- package/dist/subject-roots-backfill.d.ts.map +1 -0
- package/dist/subject-roots-backfill.js +133 -0
- package/dist/subject-roots-backfill.js.map +1 -0
- package/dist/write-precondition.d.ts +5 -1
- package/dist/write-precondition.d.ts.map +1 -1
- package/dist/write-precondition.js +11 -3
- package/dist/write-precondition.js.map +1 -1
- package/dist/writes/content-write.d.ts +170 -0
- package/dist/writes/content-write.d.ts.map +1 -0
- package/dist/writes/content-write.js +569 -0
- package/dist/writes/content-write.js.map +1 -0
- package/dist/writes/effects.d.ts +13 -0
- package/dist/writes/effects.d.ts.map +1 -0
- package/dist/writes/effects.js +28 -0
- package/dist/writes/effects.js.map +1 -0
- package/dist/writes/error-shape.d.ts +11 -0
- package/dist/writes/error-shape.d.ts.map +1 -0
- package/dist/writes/error-shape.js +100 -0
- package/dist/writes/error-shape.js.map +1 -0
- package/dist/writes/receipt-store.d.ts +81 -0
- package/dist/writes/receipt-store.d.ts.map +1 -0
- package/dist/writes/receipt-store.js +171 -0
- package/dist/writes/receipt-store.js.map +1 -0
- package/dist/writes/worker.d.ts +9 -0
- package/dist/writes/worker.d.ts.map +1 -0
- package/dist/writes/worker.js +20 -0
- package/dist/writes/worker.js.map +1 -0
- package/openapi/openapi.json +18354 -17673
- package/package.json +4 -4
package/dist/routes/data.js
CHANGED
|
@@ -2,13 +2,31 @@ import { parseLiveDeclaration } from "@x12i/memorix-data";
|
|
|
2
2
|
import { resolveScope } from "../request-scope.js";
|
|
3
3
|
import { ServiceError } from "../errors.js";
|
|
4
4
|
import { requireCallbackAccess, requireDeploymentAccess } from "../access-gate.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { runContentBatch, runContentWrite } from "../writes/content-write.js";
|
|
6
|
+
import { commitWriteEffects } from "../writes/effects.js";
|
|
7
|
+
import { patchGuardCheck } from "../patches/service.js";
|
|
8
|
+
import { resolveRecordIdAlias } from "../identity-alias.js";
|
|
7
9
|
import { maybeEnqueueNexumAnalysis } from "../nexum-analysis/enqueue.js";
|
|
10
|
+
import { listKnowledgeGraphsForSource } from "../nexum-analysis/knowledge-read.js";
|
|
8
11
|
import { memorixActivityRing, recordLooksLikeKnowledgeGraph, } from "../activity-ring.js";
|
|
9
12
|
import { getRequestCaller, requireCaller } from "../caller-identity.js";
|
|
10
|
-
import { runHookTrigger } from "../hooks/request-trigger.js";
|
|
11
13
|
import { parseSafeFilters, parseSafeFiltersValue, parseSafeSort, parseSafeSortValue } from "../safe-query.js";
|
|
14
|
+
import { declaredDiscriminatorValues } from "../catalog-bridge.js";
|
|
15
|
+
import { singleReadDiscriminatorValue, withDiscriminatorHelp } from "../single-read-discriminator.js";
|
|
16
|
+
function parseIncludeObject(raw) {
|
|
17
|
+
if (raw === undefined)
|
|
18
|
+
return undefined;
|
|
19
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
20
|
+
throw new ServiceError("VALIDATION", "include must be an object", 400);
|
|
21
|
+
}
|
|
22
|
+
const markdown = raw.markdown;
|
|
23
|
+
if (markdown === undefined)
|
|
24
|
+
return {};
|
|
25
|
+
if (typeof markdown !== "boolean") {
|
|
26
|
+
throw new ServiceError("VALIDATION", "include.markdown must be a boolean", 400);
|
|
27
|
+
}
|
|
28
|
+
return { markdown };
|
|
29
|
+
}
|
|
12
30
|
function parseContentInclude(raw) {
|
|
13
31
|
if (raw === undefined || raw === "")
|
|
14
32
|
return undefined;
|
|
@@ -19,24 +37,39 @@ function parseContentInclude(raw) {
|
|
|
19
37
|
catch {
|
|
20
38
|
throw new ServiceError("VALIDATION", "include must be JSON", 400);
|
|
21
39
|
}
|
|
22
|
-
|
|
23
|
-
throw new ServiceError("VALIDATION", "include must be an object", 400);
|
|
24
|
-
}
|
|
25
|
-
const markdown = parsed.markdown;
|
|
26
|
-
if (markdown === undefined)
|
|
27
|
-
return {};
|
|
28
|
-
if (typeof markdown !== "boolean") {
|
|
29
|
-
throw new ServiceError("VALIDATION", "include.markdown must be a boolean", 400);
|
|
30
|
-
}
|
|
31
|
-
return { markdown };
|
|
40
|
+
return parseIncludeObject(parsed);
|
|
32
41
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
/** Wildcard path so record ids that contain `/` stay intact. */
|
|
43
|
+
function subjectRecordIdFromRequest(req) {
|
|
44
|
+
const params = (req.params ?? {});
|
|
45
|
+
const raw = params["*"] ?? params.path ?? "";
|
|
46
|
+
const recordId = raw.trim();
|
|
47
|
+
if (!recordId) {
|
|
48
|
+
throw new ServiceError("VALIDATION", "recordId required", 400);
|
|
38
49
|
}
|
|
39
|
-
return
|
|
50
|
+
return recordId;
|
|
51
|
+
}
|
|
52
|
+
function publicWriteReceipt(row) {
|
|
53
|
+
return {
|
|
54
|
+
receiptId: row.receiptId,
|
|
55
|
+
status: row.status,
|
|
56
|
+
...(row.writeId ? { writeId: row.writeId } : {}),
|
|
57
|
+
createdAt: row.createdAt,
|
|
58
|
+
updatedAt: row.updatedAt,
|
|
59
|
+
...(row.receipt ? { receipt: row.receipt } : {}),
|
|
60
|
+
...(row.error
|
|
61
|
+
? {
|
|
62
|
+
error: {
|
|
63
|
+
code: row.error.code,
|
|
64
|
+
field: row.error.field,
|
|
65
|
+
message: row.error.message,
|
|
66
|
+
retryable: row.error.retryable,
|
|
67
|
+
...(row.error.retryAfterMs !== undefined ? { retryAfterMs: row.error.retryAfterMs } : {}),
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
: {}),
|
|
71
|
+
...(row.status === "pending" ? { retryAfterMs: row.error?.retryAfterMs ?? 1_000 } : {}),
|
|
72
|
+
};
|
|
40
73
|
}
|
|
41
74
|
function contentDatasource(req, body) {
|
|
42
75
|
const service = requireCaller(req);
|
|
@@ -60,7 +93,79 @@ function draftForDatasourceStamp(record) {
|
|
|
60
93
|
...(record.insights ? { insights: record.insights } : {}),
|
|
61
94
|
};
|
|
62
95
|
}
|
|
96
|
+
/** One content instance by recordId; `filter` may only select the discriminator. */
|
|
97
|
+
async function getSingleContent(platform, scope, q, recordId, include) {
|
|
98
|
+
const ports = platform.portsFor(scope);
|
|
99
|
+
const discriminatorPath = ports.catalog.getContentProfile?.(q.contentType)?.discriminator?.path;
|
|
100
|
+
const discriminatorValue = singleReadDiscriminatorValue({
|
|
101
|
+
discriminatorValue: q.discriminatorValue ?? q.discriminator,
|
|
102
|
+
filters: parseSafeFilters(q.filter),
|
|
103
|
+
discriminatorPath,
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
const record = (await ports.reader.getContent(scope, {
|
|
107
|
+
objectType: q.objectType,
|
|
108
|
+
contentType: q.contentType,
|
|
109
|
+
recordId,
|
|
110
|
+
...(discriminatorValue ? { discriminatorValue } : {}),
|
|
111
|
+
}, include ? { include } : undefined));
|
|
112
|
+
return { record, ...(discriminatorValue ? { discriminatorValue } : {}) };
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
throw withDiscriminatorHelp(err, () => discriminatorPath
|
|
116
|
+
? declaredDiscriminatorValues(platform.metadata, scope.agentIds, q.objectType, q.contentType, discriminatorPath)
|
|
117
|
+
: undefined);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
63
120
|
export async function registerDataRoutes(app, platform) {
|
|
121
|
+
app.get("/api/data/knowledge-graphs", async (req) => {
|
|
122
|
+
requireCallbackAccess(req, "explore");
|
|
123
|
+
const scope = resolveScope(req);
|
|
124
|
+
const q = req.query;
|
|
125
|
+
const sourceRecordId = q.sourceRecordId?.trim() ?? "";
|
|
126
|
+
if (!sourceRecordId) {
|
|
127
|
+
throw new ServiceError("VALIDATION", "sourceRecordId required", 400);
|
|
128
|
+
}
|
|
129
|
+
const caller = getRequestCaller(req);
|
|
130
|
+
const ports = platform.portsFor(scope);
|
|
131
|
+
const listed = await listKnowledgeGraphsForSource({
|
|
132
|
+
reader: ports.reader,
|
|
133
|
+
scope,
|
|
134
|
+
sourceRecordId,
|
|
135
|
+
limit: Number(q.limit ?? 50) || 50,
|
|
136
|
+
cursor: q.cursor,
|
|
137
|
+
});
|
|
138
|
+
memorixActivityRing.recordFetch({
|
|
139
|
+
orgId: scope.orgId,
|
|
140
|
+
caller,
|
|
141
|
+
mode: "list",
|
|
142
|
+
objectType: listed.addressing.objectType,
|
|
143
|
+
contentType: listed.addressing.contentType,
|
|
144
|
+
recordId: sourceRecordId,
|
|
145
|
+
hit: listed.records.length > 0,
|
|
146
|
+
count: listed.records.length,
|
|
147
|
+
knowledgeGraph: listed.records.length > 0,
|
|
148
|
+
});
|
|
149
|
+
return { scope, ...listed };
|
|
150
|
+
});
|
|
151
|
+
app.get("/api/data/content/context", async (req) => {
|
|
152
|
+
requireCallbackAccess(req, "explore");
|
|
153
|
+
const scope = resolveScope(req);
|
|
154
|
+
const q = req.query;
|
|
155
|
+
if (!q.objectType || !q.contentType) {
|
|
156
|
+
throw new ServiceError("VALIDATION", "objectType and contentType required", 400);
|
|
157
|
+
}
|
|
158
|
+
const recordId = resolveRecordIdAlias(q.id, q.recordId);
|
|
159
|
+
if (!recordId) {
|
|
160
|
+
throw new ServiceError("VALIDATION", "recordId required", 400);
|
|
161
|
+
}
|
|
162
|
+
const { record } = await getSingleContent(platform, scope, { ...q, objectType: q.objectType, contentType: q.contentType }, recordId);
|
|
163
|
+
const context = record.context;
|
|
164
|
+
if (!context || typeof context !== "object" || Array.isArray(context) || Object.keys(context).length === 0) {
|
|
165
|
+
throw new ServiceError("NOT_FOUND", "context is not stored on this record", 404);
|
|
166
|
+
}
|
|
167
|
+
return context;
|
|
168
|
+
});
|
|
64
169
|
app.get("/api/data/content", async (req) => {
|
|
65
170
|
requireCallbackAccess(req, "explore");
|
|
66
171
|
const scope = resolveScope(req);
|
|
@@ -75,12 +180,9 @@ export async function registerDataRoutes(app, platform) {
|
|
|
75
180
|
const recordId = resolveRecordIdAlias(q.id, q.recordId);
|
|
76
181
|
if (recordId) {
|
|
77
182
|
let record;
|
|
183
|
+
let discriminatorValue;
|
|
78
184
|
try {
|
|
79
|
-
record =
|
|
80
|
-
objectType: q.objectType,
|
|
81
|
-
contentType: q.contentType,
|
|
82
|
-
recordId,
|
|
83
|
-
}, { include }));
|
|
185
|
+
({ record, discriminatorValue } = await getSingleContent(platform, scope, { ...q, objectType: q.objectType, contentType: q.contentType }, recordId, include));
|
|
84
186
|
}
|
|
85
187
|
catch (err) {
|
|
86
188
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -126,6 +228,7 @@ export async function registerDataRoutes(app, platform) {
|
|
|
126
228
|
contentType: q.contentType,
|
|
127
229
|
id: recordId,
|
|
128
230
|
recordId,
|
|
231
|
+
...(discriminatorValue ? { discriminatorValue } : {}),
|
|
129
232
|
},
|
|
130
233
|
record,
|
|
131
234
|
currentRevision: record.revision,
|
|
@@ -135,25 +238,32 @@ export async function registerDataRoutes(app, platform) {
|
|
|
135
238
|
const limit = Math.min(Math.max(Number(q.limit ?? 50) || 50, 1), 200);
|
|
136
239
|
const filters = parseSafeFilters(q.filter);
|
|
137
240
|
const sort = parseSafeSort(q.sort);
|
|
138
|
-
if (q.count === "true"
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
241
|
+
if (q.count === "true") {
|
|
242
|
+
const indexed = platform.analytics
|
|
243
|
+
? await platform.analytics.countIndexed(scope, {
|
|
244
|
+
objectType: q.objectType,
|
|
245
|
+
contentType: q.contentType,
|
|
246
|
+
filters,
|
|
247
|
+
})
|
|
248
|
+
: null;
|
|
249
|
+
const count = indexed !== null
|
|
250
|
+
? indexed
|
|
251
|
+
: await ports.reader.countContent(scope, {
|
|
252
|
+
objectType: q.objectType,
|
|
253
|
+
contentType: q.contentType,
|
|
254
|
+
filters,
|
|
255
|
+
});
|
|
256
|
+
return {
|
|
257
|
+
scope,
|
|
258
|
+
count,
|
|
259
|
+
addressing: {
|
|
260
|
+
orgId: scope.orgId,
|
|
261
|
+
objectType: q.objectType,
|
|
262
|
+
contentType: q.contentType,
|
|
263
|
+
id: null,
|
|
264
|
+
recordId: null,
|
|
265
|
+
},
|
|
266
|
+
};
|
|
157
267
|
}
|
|
158
268
|
const listed = await ports.reader.listContent(scope, {
|
|
159
269
|
objectType: q.objectType,
|
|
@@ -222,174 +332,71 @@ export async function registerDataRoutes(app, platform) {
|
|
|
222
332
|
pageInfo: { nextCursor: listed.nextCursor ?? null, limit },
|
|
223
333
|
};
|
|
224
334
|
});
|
|
225
|
-
app.post("/api/data/content", async (req) => {
|
|
335
|
+
app.post("/api/data/content/batch", async (req) => {
|
|
226
336
|
requireDeploymentAccess(req, "write");
|
|
227
337
|
const scope = resolveScope(req);
|
|
228
|
-
const body = req.body;
|
|
229
|
-
if (!body.objectType || !body.contentType) {
|
|
230
|
-
throw new ServiceError("VALIDATION", "objectType, contentType required", 400);
|
|
231
|
-
}
|
|
232
338
|
await platform.analytics?.prepare(scope.orgId);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
339
|
+
const caller = requireCaller(req);
|
|
340
|
+
return runContentBatch({
|
|
341
|
+
scope,
|
|
342
|
+
caller,
|
|
343
|
+
datasource: contentDatasource(req, {}),
|
|
344
|
+
ports: platform.portsFor(scope),
|
|
345
|
+
receipts: platform.writeReceiptsFor(scope.orgId),
|
|
346
|
+
onCommitted: (event) => commitWriteEffects({ platform, scope, caller, event }),
|
|
347
|
+
checkWrite: patchGuardCheck(platform, scope),
|
|
348
|
+
}, req.body, (item) => contentDatasource(req, item));
|
|
349
|
+
});
|
|
350
|
+
app.get("/api/data/receipts", async (req) => {
|
|
351
|
+
requireCallbackAccess(req, "explore");
|
|
352
|
+
const scope = resolveScope(req);
|
|
353
|
+
const caller = requireCaller(req);
|
|
354
|
+
const q = req.query;
|
|
355
|
+
const status = q.status === "pending" || q.status === "stored" || q.status === "rejected" || q.status === "all"
|
|
356
|
+
? q.status
|
|
357
|
+
: "rejected";
|
|
358
|
+
const limit = Math.min(Math.max(Number(q.limit ?? 50) || 50, 1), 200);
|
|
359
|
+
const rows = await platform.writeReceiptsFor(scope.orgId).list({
|
|
360
|
+
orgId: scope.orgId,
|
|
361
|
+
caller,
|
|
362
|
+
...(status === "all" ? {} : { status }),
|
|
363
|
+
limit,
|
|
237
364
|
});
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
objectType: body.objectType,
|
|
274
|
-
contentType: body.contentType,
|
|
275
|
-
recordId: result.recordId,
|
|
276
|
-
revision: result.previousRevision,
|
|
277
|
-
outcome: "removed",
|
|
278
|
-
hooks: noted.hooks,
|
|
279
|
-
});
|
|
280
|
-
return { scope, written: true, outcome: "updated", result, precondition: body.precondition };
|
|
281
|
-
}
|
|
282
|
-
if (!body.record) {
|
|
283
|
-
throw new ServiceError("VALIDATION", "record required", 400);
|
|
284
|
-
}
|
|
285
|
-
const record = {
|
|
286
|
-
...body.record,
|
|
287
|
-
objectType: body.objectType,
|
|
288
|
-
contentType: body.contentType,
|
|
289
|
-
};
|
|
290
|
-
if (record._system && typeof record._system === "object") {
|
|
291
|
-
const system = { ...record._system };
|
|
292
|
-
delete system._datasource;
|
|
293
|
-
record._system = system;
|
|
294
|
-
}
|
|
295
|
-
let result;
|
|
296
|
-
let hooks;
|
|
297
|
-
const objectType = body.objectType;
|
|
298
|
-
const contentType = body.contentType;
|
|
299
|
-
const recordId = body.record.recordId;
|
|
300
|
-
if (op === "add" || precondition.form === "absent") {
|
|
301
|
-
const noted = await runHookTrigger(() => ports.writer.add({
|
|
302
|
-
scope,
|
|
303
|
-
precondition: { kind: "absent" },
|
|
304
|
-
record,
|
|
305
|
-
datasource,
|
|
306
|
-
suppressHooks,
|
|
307
|
-
}));
|
|
308
|
-
result = noted.value;
|
|
309
|
-
hooks = noted.hooks;
|
|
310
|
-
}
|
|
311
|
-
else if (op === "patch") {
|
|
312
|
-
const noted = await runHookTrigger(() => ports.writer.patch({
|
|
313
|
-
scope,
|
|
314
|
-
precondition: { kind: "revision", revision: Number(expectedRevision) },
|
|
315
|
-
objectType,
|
|
316
|
-
contentType,
|
|
317
|
-
recordId,
|
|
318
|
-
ops: body.patch ?? [],
|
|
319
|
-
datasource,
|
|
320
|
-
suppressHooks,
|
|
321
|
-
}));
|
|
322
|
-
result = noted.value;
|
|
323
|
-
hooks = noted.hooks;
|
|
324
|
-
}
|
|
325
|
-
else if (op === "replace") {
|
|
326
|
-
const noted = await runHookTrigger(() => ports.writer.replace({
|
|
327
|
-
scope,
|
|
328
|
-
precondition: { kind: "revision", revision: Number(expectedRevision) },
|
|
329
|
-
record,
|
|
330
|
-
datasource,
|
|
331
|
-
suppressHooks,
|
|
332
|
-
}));
|
|
333
|
-
result = noted.value;
|
|
334
|
-
hooks = noted.hooks;
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
// exact-revision upsert — never silently treat as create
|
|
338
|
-
const noted = await runHookTrigger(() => ports.writer.upsert({
|
|
339
|
-
scope,
|
|
340
|
-
precondition: { kind: "revision", revision: Number(expectedRevision) },
|
|
341
|
-
record,
|
|
342
|
-
datasource,
|
|
343
|
-
suppressHooks,
|
|
344
|
-
}));
|
|
345
|
-
result = noted.value;
|
|
346
|
-
hooks = noted.hooks;
|
|
347
|
-
}
|
|
348
|
-
const indexing = await maybeEnqueueNexumAnalysis({
|
|
349
|
-
platform,
|
|
350
|
-
scope,
|
|
351
|
-
objectType: body.objectType,
|
|
352
|
-
contentType: body.contentType,
|
|
353
|
-
recordId: result.record.recordId,
|
|
354
|
-
revision: result.record.revision,
|
|
355
|
-
data: result.record.data &&
|
|
356
|
-
typeof result.record.data === "object" &&
|
|
357
|
-
!Array.isArray(result.record.data)
|
|
358
|
-
? result.record.data
|
|
359
|
-
: body.record?.data &&
|
|
360
|
-
typeof body.record.data === "object" &&
|
|
361
|
-
!Array.isArray(body.record.data)
|
|
362
|
-
? body.record.data
|
|
363
|
-
: undefined,
|
|
364
|
-
});
|
|
365
|
-
memorixActivityRing.recordLand({
|
|
366
|
-
orgId: scope.orgId,
|
|
367
|
-
caller: getRequestCaller(req),
|
|
368
|
-
objectType: body.objectType,
|
|
369
|
-
contentType: body.contentType,
|
|
370
|
-
recordId: result.record.recordId,
|
|
371
|
-
revision: result.record.revision,
|
|
372
|
-
outcome: result.created ? "created" : "updated",
|
|
373
|
-
indexing,
|
|
374
|
-
hooks,
|
|
375
|
-
});
|
|
376
|
-
return {
|
|
377
|
-
scope,
|
|
378
|
-
written: true,
|
|
379
|
-
outcome: result.created ? "created" : "updated",
|
|
380
|
-
result,
|
|
381
|
-
currentRevision: result.record.revision,
|
|
382
|
-
precondition: body.precondition,
|
|
383
|
-
indexing,
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
catch (err) {
|
|
387
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
388
|
-
if (/stale|revision|WRITE_STALE/i.test(msg)) {
|
|
389
|
-
throw new ServiceError("REVISION_CONFLICT", msg, 409);
|
|
390
|
-
}
|
|
391
|
-
throw err;
|
|
392
|
-
}
|
|
365
|
+
return { scope, receipts: rows.map(publicWriteReceipt) };
|
|
366
|
+
});
|
|
367
|
+
app.get("/api/data/receipts/:receiptId", async (req) => {
|
|
368
|
+
requireCallbackAccess(req, "explore");
|
|
369
|
+
const scope = resolveScope(req);
|
|
370
|
+
const caller = requireCaller(req);
|
|
371
|
+
const receiptId = req.params.receiptId?.trim() ?? "";
|
|
372
|
+
if (!receiptId)
|
|
373
|
+
throw new ServiceError("VALIDATION", "receiptId required", 400, { field: "receiptId" });
|
|
374
|
+
const row = await platform.writeReceiptsFor(scope.orgId).get(scope.orgId, receiptId);
|
|
375
|
+
if (!row || row.caller !== caller) {
|
|
376
|
+
throw new ServiceError("NOT_FOUND", "receipt not found", 404, { field: "receiptId", retryable: false });
|
|
377
|
+
}
|
|
378
|
+
return publicWriteReceipt(row);
|
|
379
|
+
});
|
|
380
|
+
app.post("/api/data/content", async (req, reply) => {
|
|
381
|
+
requireDeploymentAccess(req, "write");
|
|
382
|
+
const scope = resolveScope(req);
|
|
383
|
+
const body = (req.body ?? {});
|
|
384
|
+
await platform.analytics?.prepare(scope.orgId);
|
|
385
|
+
const caller = requireCaller(req);
|
|
386
|
+
const outcome = await runContentWrite({
|
|
387
|
+
scope,
|
|
388
|
+
caller,
|
|
389
|
+
datasource: contentDatasource(req, body),
|
|
390
|
+
ports: platform.portsFor(scope),
|
|
391
|
+
receipts: platform.writeReceiptsFor(scope.orgId),
|
|
392
|
+
onCommitted: (event) => commitWriteEffects({ platform, scope, caller, event }),
|
|
393
|
+
checkWrite: patchGuardCheck(platform, scope),
|
|
394
|
+
}, body);
|
|
395
|
+
if (outcome.kind === "rejected")
|
|
396
|
+
throw outcome.error;
|
|
397
|
+
if (outcome.kind === "pending")
|
|
398
|
+
return reply.code(202).send(outcome.body);
|
|
399
|
+
return outcome.body;
|
|
393
400
|
});
|
|
394
401
|
app.post("/api/data/content/move-fields", async (req) => {
|
|
395
402
|
requireDeploymentAccess(req, "write");
|
|
@@ -540,15 +547,37 @@ export async function registerDataRoutes(app, platform) {
|
|
|
540
547
|
pageInfo: recordId ? undefined : { nextCursor, limit },
|
|
541
548
|
};
|
|
542
549
|
});
|
|
550
|
+
app.get("/api/data/subjects/*", async (req) => {
|
|
551
|
+
requireCallbackAccess(req, "explore");
|
|
552
|
+
const scope = resolveScope(req);
|
|
553
|
+
const recordId = subjectRecordIdFromRequest(req);
|
|
554
|
+
const ports = platform.portsFor(scope);
|
|
555
|
+
const reader = ports.reader;
|
|
556
|
+
if (typeof reader.getSubject !== "function") {
|
|
557
|
+
throw new ServiceError("CANONICAL_PORT_MISSING", "Subject catalog requires @x12i/memorix-data with getSubject", 501);
|
|
558
|
+
}
|
|
559
|
+
const entry = await reader.getSubject(scope, recordId);
|
|
560
|
+
if (!entry) {
|
|
561
|
+
throw new ServiceError("READ_NOT_FOUND", `Subject ${recordId} is not reserved`, 404);
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
scope,
|
|
565
|
+
recordId: entry.recordId,
|
|
566
|
+
objectType: entry.objectType,
|
|
567
|
+
dataCategory: entry.dataCategory,
|
|
568
|
+
contentTypes: entry.contentTypes,
|
|
569
|
+
};
|
|
570
|
+
});
|
|
543
571
|
app.post("/api/data/compose", async (req) => {
|
|
544
572
|
requireCallbackAccess(req, "explore");
|
|
545
573
|
const scope = resolveScope(req);
|
|
546
574
|
const body = parseComposeBody(req.body);
|
|
547
575
|
const ports = platform.portsFor(scope);
|
|
548
|
-
const
|
|
576
|
+
const includeMarkdown = body.include?.markdown === true;
|
|
577
|
+
const composition = (await ports.reader.composeSubject(scope, {
|
|
549
578
|
recordId: body.recordId,
|
|
550
579
|
contentTypes: body.contentTypes,
|
|
551
|
-
});
|
|
580
|
+
}, includeMarkdown ? { include: { markdown: true } } : undefined));
|
|
552
581
|
// Flatten sibling content instances for operator UI — every instance must match subject.
|
|
553
582
|
const instances = [];
|
|
554
583
|
for (const [contentType, list] of Object.entries(composition.contents ?? {})) {
|
|
@@ -561,6 +590,7 @@ export async function registerDataRoutes(app, platform) {
|
|
|
561
590
|
dataCategory: composition.dataCategory,
|
|
562
591
|
revision: rec.revision ?? null,
|
|
563
592
|
data: rec.data ?? null,
|
|
593
|
+
...(includeMarkdown && rec._markdown !== undefined ? { _markdown: rec._markdown } : {}),
|
|
564
594
|
});
|
|
565
595
|
}
|
|
566
596
|
}
|
|
@@ -727,6 +757,8 @@ export async function registerDataRoutes(app, platform) {
|
|
|
727
757
|
if (!body.leadingContentType?.trim()) {
|
|
728
758
|
throw new ServiceError("VALIDATION", "leadingContentType required", 400);
|
|
729
759
|
}
|
|
760
|
+
const include = parseIncludeObject(body.include);
|
|
761
|
+
const includeMarkdown = include?.markdown === true;
|
|
730
762
|
const limit = Math.min(Math.max(Number(body.limit ?? 50) || 50, 1), 200);
|
|
731
763
|
const filters = parseSafeFiltersValue(body.filter);
|
|
732
764
|
const sort = parseSafeSortValue(body.sort);
|
|
@@ -739,11 +771,20 @@ export async function registerDataRoutes(app, platform) {
|
|
|
739
771
|
sort,
|
|
740
772
|
limit,
|
|
741
773
|
cursor: body.cursor,
|
|
774
|
+
...(includeMarkdown ? { include: { markdown: true } } : {}),
|
|
742
775
|
});
|
|
743
776
|
const items = listed.items.map((composition) => {
|
|
744
777
|
const contents = {};
|
|
745
778
|
for (const [contentType, list] of Object.entries(composition.contents ?? {})) {
|
|
746
|
-
contents[contentType] = list.map((rec) =>
|
|
779
|
+
contents[contentType] = list.map((rec) => {
|
|
780
|
+
if (!includeMarkdown)
|
|
781
|
+
return rec.data ?? null;
|
|
782
|
+
return {
|
|
783
|
+
data: rec.data ?? null,
|
|
784
|
+
revision: rec.revision ?? null,
|
|
785
|
+
...(rec._markdown !== undefined ? { _markdown: rec._markdown } : {}),
|
|
786
|
+
};
|
|
787
|
+
});
|
|
747
788
|
}
|
|
748
789
|
return {
|
|
749
790
|
recordId: composition.recordId,
|
|
@@ -802,6 +843,7 @@ function parseComposeBody(raw) {
|
|
|
802
843
|
return {
|
|
803
844
|
recordId: body.recordId,
|
|
804
845
|
contentTypes: body.contentTypes,
|
|
846
|
+
include: parseIncludeObject(body.include),
|
|
805
847
|
};
|
|
806
848
|
}
|
|
807
849
|
function assertComposeInstanceRecordId(subjectId, rec) {
|