@sixb/core 0.1.14 → 0.1.16
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/dist/actions/index.js +26 -26
- package/dist/actions/worker.js +1 -1
- package/dist/agents/index.js +41 -41
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/streams/index.js +2 -2
- package/dist/agents/streams/internal.js +2 -2
- package/dist/auth/index.js +6 -6
- package/dist/authorization/index.js +3 -3
- package/dist/bootstrap/index.js +44 -44
- package/dist/{chunk-6rzb1g6h.js → chunk-2xvgy3x5.js} +2 -2
- package/dist/{chunk-16jenmzw.js → chunk-3q7z9hd4.js} +33 -33
- package/dist/{chunk-16jenmzw.js.map → chunk-3q7z9hd4.js.map} +1 -1
- package/dist/{chunk-hxgj4kzd.js → chunk-40qb8wpe.js} +11 -11
- package/dist/{chunk-hxgj4kzd.js.map → chunk-40qb8wpe.js.map} +3 -3
- package/dist/{chunk-vn3yh259.js → chunk-4gmng3vf.js} +1 -1
- package/dist/{chunk-znw0zvy4.js → chunk-4wn7bxxa.js} +25 -7
- package/dist/chunk-4wn7bxxa.js.map +12 -0
- package/dist/{chunk-gct3m1da.js → chunk-667x4drs.js} +3 -3
- package/dist/{chunk-rzcjp5m2.js → chunk-69kt9q43.js} +2 -2
- package/dist/{chunk-3qpcrcj8.js → chunk-6aawea28.js} +2 -4
- package/dist/chunk-6aawea28.js.map +10 -0
- package/dist/{chunk-rzkr70gr.js → chunk-6fsr7y93.js} +5 -1
- package/dist/{chunk-rzkr70gr.js.map → chunk-6fsr7y93.js.map} +3 -3
- package/dist/{chunk-3fpgxpsq.js → chunk-6z1q7j19.js} +144 -73
- package/dist/{chunk-3fpgxpsq.js.map → chunk-6z1q7j19.js.map} +6 -6
- package/dist/{chunk-ypj2wqtj.js → chunk-70jmg2zx.js} +2 -2
- package/dist/{chunk-m8zkvep1.js → chunk-7g2gjvah.js} +1 -1
- package/dist/{chunk-pvgpckm1.js → chunk-8x74221e.js} +10 -10
- package/dist/{chunk-ma1mhh7w.js → chunk-a7ne02hk.js} +6 -6
- package/dist/{chunk-bxx3ms5t.js → chunk-b9y8hcm2.js} +2 -2
- package/dist/{chunk-ww185kt6.js → chunk-chdg6eqq.js} +2 -2
- package/dist/{chunk-y487ywrv.js → chunk-cq44v1a6.js} +4 -4
- package/dist/{chunk-4ypbe95r.js → chunk-cxj8y8t0.js} +2 -2
- package/dist/{chunk-3a338wrp.js → chunk-dp7yk20b.js} +1 -1
- package/dist/{chunk-dv4ct8mp.js → chunk-dsv8kdgk.js} +4 -4
- package/dist/{chunk-dsfw1bdr.js → chunk-f72rbvsb.js} +2 -2
- package/dist/{chunk-4tnrzqqh.js → chunk-fj1dvp37.js} +1 -1
- package/dist/{chunk-ht4a7mjg.js → chunk-g4rbm4a6.js} +42 -8
- package/dist/{chunk-ht4a7mjg.js.map → chunk-g4rbm4a6.js.map} +3 -3
- package/dist/{chunk-wnxabtn4.js → chunk-gkxsenvp.js} +19 -11
- package/dist/{chunk-wnxabtn4.js.map → chunk-gkxsenvp.js.map} +4 -4
- package/dist/{chunk-vef6qa14.js → chunk-gmdvns8f.js} +2 -2
- package/dist/{chunk-728fp090.js → chunk-h044jb97.js} +1072 -1075
- package/dist/chunk-h044jb97.js.map +15 -0
- package/dist/{chunk-00pfcqh4.js → chunk-h4138ww2.js} +130 -71
- package/dist/{chunk-00pfcqh4.js.map → chunk-h4138ww2.js.map} +5 -5
- package/dist/{chunk-yvf2jqj8.js → chunk-h866d223.js} +1 -1
- package/dist/{chunk-qma80ych.js → chunk-hn2r7z41.js} +3 -2
- package/dist/chunk-hn2r7z41.js.map +11 -0
- package/dist/{chunk-bdhfn0ww.js → chunk-kkqwzy6h.js} +1 -1
- package/dist/{chunk-wn7mznxc.js → chunk-kttr5ftm.js} +5 -5
- package/dist/{chunk-kxea54yg.js → chunk-kvysp909.js} +3 -3
- package/dist/{chunk-p1683c0v.js → chunk-kyjcwqz6.js} +5 -2
- package/dist/{chunk-p1683c0v.js.map → chunk-kyjcwqz6.js.map} +3 -3
- package/dist/{chunk-ffwvdgk1.js → chunk-nj0020ay.js} +1 -1
- package/dist/{chunk-q2xghyft.js → chunk-nkbev3tr.js} +5 -5
- package/dist/{chunk-cbn96bve.js → chunk-nvymebc1.js} +2 -2
- package/dist/{chunk-2fv8sajb.js → chunk-qnd7ehe5.js} +2 -2
- package/dist/{chunk-jfdggg1p.js → chunk-sbcm1b92.js} +5 -5
- package/dist/{chunk-d8yes9yx.js → chunk-swxzncrv.js} +1 -1
- package/dist/{chunk-drz59ve0.js → chunk-tkdbge6v.js} +1 -1
- package/dist/{chunk-ky7zpbtt.js → chunk-tnvafr7g.js} +1 -1
- package/dist/{chunk-y6vnvayj.js → chunk-vjcd8d27.js} +1 -1
- package/dist/{chunk-hj0n3c2p.js → chunk-vjqt7q3a.js} +45 -27
- package/dist/{chunk-hj0n3c2p.js.map → chunk-vjqt7q3a.js.map} +3 -3
- package/dist/{chunk-z9h698s8.js → chunk-vsvrv7n8.js} +1 -1
- package/dist/{chunk-xgptywp0.js → chunk-y2aq6ag9.js} +1 -1
- package/dist/{chunk-e2c5yxfe.js → chunk-zb47r1c8.js} +2 -2
- package/dist/{chunk-3nx8vj5a.js → chunk-zvmr2hqk.js} +3 -2
- package/dist/chunk-zvmr2hqk.js.map +12 -0
- package/dist/deploy/define.d.ts +4 -0
- package/dist/deploy/define.d.ts.map +1 -0
- package/dist/deploy/index.d.ts +3 -0
- package/dist/deploy/index.d.ts.map +1 -0
- package/dist/deploy/index.js +10 -0
- package/dist/deploy/index.js.map +10 -0
- package/dist/deploy/internal.d.ts +3 -0
- package/dist/deploy/internal.d.ts.map +1 -0
- package/dist/deploy/internal.js +244 -0
- package/dist/deploy/internal.js.map +11 -0
- package/dist/deploy/services.d.ts +8 -0
- package/dist/deploy/services.d.ts.map +1 -0
- package/dist/deploy/types.d.ts +309 -0
- package/dist/deploy/types.d.ts.map +1 -0
- package/dist/deploy/validation.d.ts +7 -0
- package/dist/deploy/validation.d.ts.map +1 -0
- package/dist/error-reporting/internal.js +3 -3
- package/dist/errors/catalog.d.ts +4 -0
- package/dist/errors/catalog.d.ts.map +1 -1
- package/dist/errors/internal.js +1 -1
- package/dist/events/index.js +3 -3
- package/dist/execution/agent.js +38 -38
- package/dist/execution/primitive.js +34 -34
- package/dist/execution/request.js +36 -36
- package/dist/execution/request.js.map +1 -1
- package/dist/index.js +56 -56
- package/dist/index.js.map +1 -1
- package/dist/maintenance/ontology-maintenance.d.ts +2 -0
- package/dist/maintenance/ontology-maintenance.d.ts.map +1 -1
- package/dist/maintenance/types.d.ts +8 -2
- package/dist/maintenance/types.d.ts.map +1 -1
- package/dist/materialization/dataset-watermark.d.ts +6 -1
- package/dist/materialization/dataset-watermark.d.ts.map +1 -1
- package/dist/materialization/index.js +1 -1
- package/dist/materialization/model.d.ts +6 -0
- package/dist/materialization/model.d.ts.map +1 -1
- package/dist/materializer/index.js +22 -22
- package/dist/materializer/index.js.map +1 -1
- package/dist/materializer/projections/finish-run.d.ts.map +1 -1
- package/dist/materializer/projections/replace.d.ts.map +1 -1
- package/dist/materializer/projections/source-materialization.d.ts +6 -2
- package/dist/materializer/projections/source-materialization.d.ts.map +1 -1
- package/dist/models/embedding-model.d.ts +4 -0
- package/dist/models/embedding-model.d.ts.map +1 -1
- package/dist/models/execution/index.js +19 -19
- package/dist/models/index.js +2 -2
- package/dist/objects/index.js +25 -25
- package/dist/objects/query/index.js +21 -21
- package/dist/objects/query/index.js.map +1 -1
- package/dist/objects/vectors/indexing-generate.d.ts.map +1 -1
- package/dist/objects/vectors/profile.d.ts.map +1 -1
- package/dist/objects/vectors/query.d.ts +1 -1
- package/dist/objects/vectors/query.d.ts.map +1 -1
- package/dist/objects/vectors/resolve-query.d.ts.map +1 -1
- package/dist/objects/vectors/types.d.ts +1 -1
- package/dist/objects/vectors/types.d.ts.map +1 -1
- package/dist/ontology/builders.d.ts +13 -4
- package/dist/ontology/builders.d.ts.map +1 -1
- package/dist/ontology/index.js +3 -3
- package/dist/ontology/internal.js +2 -2
- package/dist/ontology/vector-profiles.d.ts.map +1 -1
- package/dist/pipelines/index.js +23 -23
- package/dist/projections/internal.js +20 -20
- package/dist/projections/types.d.ts +1 -1
- package/dist/projections/types.d.ts.map +1 -1
- package/dist/queues/in-memory.d.ts +1 -1
- package/dist/queues/in-memory.d.ts.map +1 -1
- package/dist/runtime/internal.js +23 -23
- package/dist/sandboxes/index.js +5 -5
- package/dist/shares/internal.js +18 -18
- package/dist/shares/internal.js.map +1 -1
- package/dist/storage/action-runs/failure.js +2 -2
- package/dist/storage/agents/provider.js +1 -1
- package/dist/storage/ai-limits/enforcement.js +2 -2
- package/dist/storage/connector-connections/provider.js +1 -1
- package/dist/storage/file-upload-sessions/provider.js +7 -7
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +33 -33
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/internal.js +1 -1
- package/dist/storage/ontology/in-memory/sources.d.ts +6 -3
- package/dist/storage/ontology/in-memory/sources.d.ts.map +1 -1
- package/dist/storage/ontology/index.d.ts +1 -1
- package/dist/storage/ontology/index.d.ts.map +1 -1
- package/dist/storage/ontology/provider.d.ts +16 -3
- package/dist/storage/ontology/provider.d.ts.map +1 -1
- package/dist/storage/ontology/provider.js +8 -4
- package/dist/storage/ontology/provider.js.map +1 -1
- package/dist/storage/ontology/sources.d.ts +36 -8
- package/dist/storage/ontology/sources.d.ts.map +1 -1
- package/dist/storage/projection-runs/in-memory.d.ts +3 -1
- package/dist/storage/projection-runs/in-memory.d.ts.map +1 -1
- package/dist/storage/projection-runs/index.d.ts +1 -1
- package/dist/storage/projection-runs/index.d.ts.map +1 -1
- package/dist/storage/projection-runs/lifecycle.d.ts +12 -0
- package/dist/storage/projection-runs/lifecycle.d.ts.map +1 -1
- package/dist/storage/projection-runs/provider.js +10 -4
- package/dist/storage/projection-runs/provider.js.map +1 -1
- package/dist/storage/projection-runs/types.d.ts +32 -1
- package/dist/storage/projection-runs/types.d.ts.map +1 -1
- package/dist/storage/workflow-runs/provider.js +7 -7
- package/dist/syncs/index.js +24 -24
- package/dist/testing/index.js +306 -64
- package/dist/testing/index.js.map +4 -4
- package/dist/testing/ontology-storage-contract.d.ts.map +1 -1
- package/dist/testing/projection-run-storage-contract.d.ts.map +1 -1
- package/dist/webhooks/internal.js +19 -19
- package/dist/webhooks/internal.js.map +1 -1
- package/dist/workers/index.js +1 -1
- package/dist/workflows/index.js +41 -41
- package/package.json +13 -1
- package/src/deploy/define.ts +6 -0
- package/src/deploy/index.ts +41 -0
- package/src/deploy/internal.ts +7 -0
- package/src/deploy/services.ts +51 -0
- package/src/deploy/types.ts +338 -0
- package/src/deploy/validation.ts +261 -0
- package/src/errors/catalog.ts +4 -0
- package/src/maintenance/ontology-maintenance.ts +32 -2
- package/src/maintenance/types.ts +8 -2
- package/src/materialization/dataset-watermark.ts +6 -4
- package/src/materialization/model.ts +7 -0
- package/src/materializer/README.md +26 -6
- package/src/materializer/projections/finish-run.ts +129 -26
- package/src/materializer/projections/replace.ts +67 -49
- package/src/materializer/projections/source-materialization.ts +11 -6
- package/src/models/embedding-model.ts +12 -0
- package/src/models/execution/embedding.ts +5 -1
- package/src/objects/query/validate.ts +1 -10
- package/src/objects/vectors/README.md +8 -0
- package/src/objects/vectors/handle.ts +1 -1
- package/src/objects/vectors/indexing-generate.ts +5 -1
- package/src/objects/vectors/profile.ts +2 -0
- package/src/objects/vectors/query.ts +18 -4
- package/src/objects/vectors/resolve-query.ts +9 -5
- package/src/objects/vectors/types.ts +3 -1
- package/src/ontology/builders.ts +21 -7
- package/src/ontology/vector-profiles.ts +1 -0
- package/src/projections/types.ts +1 -0
- package/src/storage/index.ts +6 -1
- package/src/storage/ontology/in-memory/sources.ts +119 -55
- package/src/storage/ontology/index.ts +4 -1
- package/src/storage/ontology/provider.ts +64 -7
- package/src/storage/ontology/sources.ts +42 -8
- package/src/storage/projection-runs/in-memory.ts +41 -2
- package/src/storage/projection-runs/index.ts +2 -0
- package/src/storage/projection-runs/lifecycle.ts +61 -4
- package/src/storage/projection-runs/types.ts +41 -1
- package/src/testing/ontology-storage-contract.ts +141 -15
- package/src/testing/projection-run-storage-contract.ts +174 -1
- package/dist/chunk-3nx8vj5a.js.map +0 -12
- package/dist/chunk-3qpcrcj8.js.map +0 -10
- package/dist/chunk-728fp090.js.map +0 -15
- package/dist/chunk-qma80ych.js.map +0 -11
- package/dist/chunk-znw0zvy4.js.map +0 -12
- /package/dist/{chunk-6rzb1g6h.js.map → chunk-2xvgy3x5.js.map} +0 -0
- /package/dist/{chunk-vn3yh259.js.map → chunk-4gmng3vf.js.map} +0 -0
- /package/dist/{chunk-gct3m1da.js.map → chunk-667x4drs.js.map} +0 -0
- /package/dist/{chunk-rzcjp5m2.js.map → chunk-69kt9q43.js.map} +0 -0
- /package/dist/{chunk-ypj2wqtj.js.map → chunk-70jmg2zx.js.map} +0 -0
- /package/dist/{chunk-m8zkvep1.js.map → chunk-7g2gjvah.js.map} +0 -0
- /package/dist/{chunk-pvgpckm1.js.map → chunk-8x74221e.js.map} +0 -0
- /package/dist/{chunk-ma1mhh7w.js.map → chunk-a7ne02hk.js.map} +0 -0
- /package/dist/{chunk-bxx3ms5t.js.map → chunk-b9y8hcm2.js.map} +0 -0
- /package/dist/{chunk-ww185kt6.js.map → chunk-chdg6eqq.js.map} +0 -0
- /package/dist/{chunk-y487ywrv.js.map → chunk-cq44v1a6.js.map} +0 -0
- /package/dist/{chunk-4ypbe95r.js.map → chunk-cxj8y8t0.js.map} +0 -0
- /package/dist/{chunk-3a338wrp.js.map → chunk-dp7yk20b.js.map} +0 -0
- /package/dist/{chunk-dv4ct8mp.js.map → chunk-dsv8kdgk.js.map} +0 -0
- /package/dist/{chunk-dsfw1bdr.js.map → chunk-f72rbvsb.js.map} +0 -0
- /package/dist/{chunk-4tnrzqqh.js.map → chunk-fj1dvp37.js.map} +0 -0
- /package/dist/{chunk-vef6qa14.js.map → chunk-gmdvns8f.js.map} +0 -0
- /package/dist/{chunk-yvf2jqj8.js.map → chunk-h866d223.js.map} +0 -0
- /package/dist/{chunk-bdhfn0ww.js.map → chunk-kkqwzy6h.js.map} +0 -0
- /package/dist/{chunk-wn7mznxc.js.map → chunk-kttr5ftm.js.map} +0 -0
- /package/dist/{chunk-kxea54yg.js.map → chunk-kvysp909.js.map} +0 -0
- /package/dist/{chunk-ffwvdgk1.js.map → chunk-nj0020ay.js.map} +0 -0
- /package/dist/{chunk-q2xghyft.js.map → chunk-nkbev3tr.js.map} +0 -0
- /package/dist/{chunk-cbn96bve.js.map → chunk-nvymebc1.js.map} +0 -0
- /package/dist/{chunk-2fv8sajb.js.map → chunk-qnd7ehe5.js.map} +0 -0
- /package/dist/{chunk-jfdggg1p.js.map → chunk-sbcm1b92.js.map} +0 -0
- /package/dist/{chunk-d8yes9yx.js.map → chunk-swxzncrv.js.map} +0 -0
- /package/dist/{chunk-drz59ve0.js.map → chunk-tkdbge6v.js.map} +0 -0
- /package/dist/{chunk-ky7zpbtt.js.map → chunk-tnvafr7g.js.map} +0 -0
- /package/dist/{chunk-y6vnvayj.js.map → chunk-vjcd8d27.js.map} +0 -0
- /package/dist/{chunk-z9h698s8.js.map → chunk-vsvrv7n8.js.map} +0 -0
- /package/dist/{chunk-xgptywp0.js.map → chunk-y2aq6ag9.js.map} +0 -0
- /package/dist/{chunk-e2c5yxfe.js.map → chunk-zb47r1c8.js.map} +0 -0
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
MAX_OBJECT_READ_FACETS2,
|
|
4
4
|
isAllowed2,
|
|
5
5
|
assertAuthorized2
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-69kt9q43.js";
|
|
7
7
|
import {
|
|
8
8
|
validateSchemaValue2,
|
|
9
9
|
validatePropertyValue2,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isTextSchema,
|
|
13
13
|
isContainsSchema,
|
|
14
14
|
queryScalarKindForSchema
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-swxzncrv.js";
|
|
16
16
|
import {
|
|
17
17
|
hasVectorProfile2,
|
|
18
18
|
isVectorProfileQuery2,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
MAX_VECTOR_K,
|
|
25
25
|
vectorConfiguration,
|
|
26
26
|
normalizeVector2
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-6z1q7j19.js";
|
|
28
28
|
import {
|
|
29
29
|
normalizeObjectQuery2,
|
|
30
30
|
ObjectQueryValidationError2,
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
} from "./chunk-a0073121.js";
|
|
34
34
|
import {
|
|
35
35
|
sameEmbeddingModel
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-kyjcwqz6.js";
|
|
37
37
|
import {
|
|
38
38
|
formatUnknownObjectTypeMessage
|
|
39
39
|
} from "./chunk-cqr3acm1.js";
|
|
@@ -43,1214 +43,1211 @@ import {
|
|
|
43
43
|
import {
|
|
44
44
|
stableJsonStringify2
|
|
45
45
|
} from "./chunk-wy0qdqfm.js";
|
|
46
|
-
// src/objects/
|
|
47
|
-
var
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
const model = models?.getByRef({
|
|
71
|
-
provider: profile.model.providerId,
|
|
72
|
-
modelId: profile.model.modelId
|
|
73
|
-
})?.model;
|
|
74
|
-
if (!model || !sameEmbeddingModel(model, profile.model)) {
|
|
75
|
-
throw new ObjectQueryExecutionError2("embedding_model_unavailable", "Vector search requires the embedding model registered for its profile.");
|
|
76
|
-
}
|
|
77
|
-
return model;
|
|
78
|
-
}
|
|
79
|
-
async function embedSearchText(text, model, signal) {
|
|
80
|
-
const dimensions = model.definition.dimensions;
|
|
81
|
-
const timeout = AbortSignal.timeout(QUERY_EMBEDDING_TIMEOUT_MS);
|
|
82
|
-
const requestSignal = signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
83
|
-
requestSignal.throwIfAborted();
|
|
84
|
-
const result = await model.embed({ texts: [text], signal: requestSignal });
|
|
85
|
-
requestSignal.throwIfAborted();
|
|
86
|
-
if (!Array.isArray(result?.vectors) || result.vectors.length !== 1) {
|
|
87
|
-
throw new ObjectQueryExecutionError2("invalid_embedding_response", "Embedding model must return exactly one vector for the search text.");
|
|
88
|
-
}
|
|
89
|
-
try {
|
|
90
|
-
return normalizeVector2(result.vectors[0], dimensions);
|
|
91
|
-
} catch {
|
|
92
|
-
throw new ObjectQueryExecutionError2("invalid_embedding_response", "Embedding model returned an invalid vector for the search profile.");
|
|
93
|
-
}
|
|
46
|
+
// src/objects/query/validate.ts
|
|
47
|
+
var DEFAULT_MAX_LIMIT = 1000;
|
|
48
|
+
var DEFAULT_MAX_PAGE_SIZE = 1000;
|
|
49
|
+
var DEFAULT_MAX_REFS = 1000;
|
|
50
|
+
var NOOP_ADMISSION_STATE = Object.freeze({});
|
|
51
|
+
var NOOP_QUERY_ADMISSION = Object.freeze({
|
|
52
|
+
empty: () => NOOP_ADMISSION_STATE,
|
|
53
|
+
source: () => ({ state: NOOP_ADMISSION_STATE }),
|
|
54
|
+
property: () => {
|
|
55
|
+
return;
|
|
56
|
+
},
|
|
57
|
+
edge: (input) => ({
|
|
58
|
+
state: input.state
|
|
59
|
+
}),
|
|
60
|
+
set: (input) => input.states[0] ?? NOOP_ADMISSION_STATE
|
|
61
|
+
});
|
|
62
|
+
function validateObjectQuery2(query, options) {
|
|
63
|
+
const admitted = validateObjectQueryWithAdmission(query, options, NOOP_QUERY_ADMISSION);
|
|
64
|
+
return {
|
|
65
|
+
query: admitted.query,
|
|
66
|
+
result: admitted.result,
|
|
67
|
+
touchedObjectTypeIds: admitted.touchedObjectTypeIds
|
|
68
|
+
};
|
|
94
69
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"sort",
|
|
103
|
-
"limit",
|
|
104
|
-
"page",
|
|
105
|
-
"project",
|
|
106
|
-
"expand"
|
|
107
|
-
]);
|
|
108
|
-
function planObjectQuery2(query, options) {
|
|
109
|
-
const operation = options.operation ?? "queryObjects";
|
|
110
|
-
const providerIssues = collectProviderIssues(query, options.capabilities, {
|
|
111
|
-
operation,
|
|
112
|
-
hasImplementation: hasProviderImplementation(operation, options)
|
|
113
|
-
});
|
|
114
|
-
if (providerIssues.length === 0) {
|
|
115
|
-
return {
|
|
116
|
-
mode: "pushdown",
|
|
117
|
-
query,
|
|
118
|
-
providerIssues,
|
|
119
|
-
fallbackIssues: [],
|
|
120
|
-
issues: []
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
const fallbackIssues = collectFallbackIssues(query, options);
|
|
124
|
-
const issues = [...providerIssues, ...fallbackIssues];
|
|
125
|
-
if (fallbackIssues.length === 0) {
|
|
126
|
-
return {
|
|
127
|
-
mode: "fallback",
|
|
128
|
-
query,
|
|
129
|
-
providerIssues,
|
|
130
|
-
fallbackIssues,
|
|
131
|
-
issues: [],
|
|
132
|
-
fallback: {
|
|
133
|
-
maxRows: options.maxFallbackRows ?? DEFAULT_MAX_FALLBACK_ROWS,
|
|
134
|
-
requiresExplicitBound: options.requiresExplicitFallbackBound !== false
|
|
135
|
-
}
|
|
136
|
-
};
|
|
70
|
+
function validateObjectQueryWithAdmission(query, options, admission) {
|
|
71
|
+
const normalized = options.normalize === false ? query : normalizeObjectQuery2(query);
|
|
72
|
+
const ctx = createValidationContext(options, admission);
|
|
73
|
+
validateVectorComposition(query, ctx);
|
|
74
|
+
const validation = validateQueryNode(normalized, "$", ctx);
|
|
75
|
+
if (ctx.issues.length > 0) {
|
|
76
|
+
throw new ObjectQueryValidationError2(ctx.issues);
|
|
137
77
|
}
|
|
78
|
+
const denial = ctx.admissionDenials[0];
|
|
79
|
+
if (denial)
|
|
80
|
+
throw denial;
|
|
138
81
|
return {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
issues
|
|
82
|
+
query: validation.query,
|
|
83
|
+
result: validation.result,
|
|
84
|
+
touchedObjectTypeIds: [...ctx.touchedObjectTypeIds],
|
|
85
|
+
admissionState: validation.admissionState
|
|
144
86
|
};
|
|
145
87
|
}
|
|
146
|
-
function
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
88
|
+
function collectObjectQueryValidationIssues2(query, options) {
|
|
89
|
+
const normalized = options.normalize === false ? query : normalizeObjectQuery2(query);
|
|
90
|
+
const ctx = createValidationContext(options, NOOP_QUERY_ADMISSION);
|
|
91
|
+
validateVectorComposition(query, ctx);
|
|
92
|
+
validateQueryNode(normalized, "$", ctx);
|
|
93
|
+
return ctx.issues;
|
|
151
94
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
if (!enabled) {
|
|
155
|
-
const code = `${operationIssuePrefix(options.operation)}_not_enabled`;
|
|
156
|
-
addIssue(issues, "$", code, `Object storage capabilities do not enable ${options.operation} pushdown`);
|
|
157
|
-
}
|
|
158
|
-
if (options.hasImplementation)
|
|
159
|
-
return;
|
|
160
|
-
const code = `${operationIssuePrefix(options.operation)}_not_implemented`;
|
|
161
|
-
addIssue(issues, "$", code, `Object storage does not implement ${options.operation}`);
|
|
95
|
+
function resolveObjectQueryResultShape2(query, options) {
|
|
96
|
+
return validateObjectQuery2(query, options).result;
|
|
162
97
|
}
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return options.hasExistsObjects === true;
|
|
171
|
-
case "facetObjects":
|
|
172
|
-
return options.hasFacetObjects === true;
|
|
173
|
-
}
|
|
98
|
+
function validateVectorComposition(query, ctx) {
|
|
99
|
+
if (hasVectorProfile2(query) && !isVectorProfileQuery2(query))
|
|
100
|
+
ctx.issues.push({
|
|
101
|
+
path: "$",
|
|
102
|
+
code: "vector.composition",
|
|
103
|
+
message: "Vector profiles support one top-k over one concrete type, with filters, traversals or sets before ranking and optional limit/project after it."
|
|
104
|
+
});
|
|
174
105
|
}
|
|
175
|
-
function
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
106
|
+
function createValidationContext(options, admission) {
|
|
107
|
+
return {
|
|
108
|
+
ontology: options.ontology,
|
|
109
|
+
valueTypesById: options.ontology.getValueTypesById(),
|
|
110
|
+
maxLimit: options.maxLimit ?? DEFAULT_MAX_LIMIT,
|
|
111
|
+
maxPageSize: options.maxPageSize ?? DEFAULT_MAX_PAGE_SIZE,
|
|
112
|
+
maxRefs: options.maxRefs ?? DEFAULT_MAX_REFS,
|
|
113
|
+
issues: [],
|
|
114
|
+
touchedObjectTypeIds: new Set,
|
|
115
|
+
admission,
|
|
116
|
+
admissionDenials: []
|
|
117
|
+
};
|
|
186
118
|
}
|
|
187
|
-
function
|
|
188
|
-
|
|
189
|
-
|
|
119
|
+
function validateQueryNode(query, path, ctx) {
|
|
120
|
+
const validation = dispatchQueryNode(query, path, ctx);
|
|
121
|
+
for (const objectTypeId of validation.result.objectTypeIds) {
|
|
122
|
+
ctx.touchedObjectTypeIds.add(objectTypeId);
|
|
190
123
|
}
|
|
124
|
+
return validation;
|
|
125
|
+
}
|
|
126
|
+
function dispatchQueryNode(query, path, ctx) {
|
|
191
127
|
switch (query.kind) {
|
|
192
|
-
case "start":
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
case "
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
case "
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
128
|
+
case "start": {
|
|
129
|
+
const validation = validateStart(query.objectTypeId, query.includeSubtypes, path, ctx);
|
|
130
|
+
return {
|
|
131
|
+
...validation,
|
|
132
|
+
admissionState: admitTransition(ctx, ctx.admission.source({
|
|
133
|
+
kind: "start",
|
|
134
|
+
result: validation.result,
|
|
135
|
+
objectTypeId: query.objectTypeId,
|
|
136
|
+
path
|
|
137
|
+
}))
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
case "refs": {
|
|
141
|
+
const validation = validateRefs(query.refs, path, ctx);
|
|
142
|
+
return {
|
|
143
|
+
...validation,
|
|
144
|
+
admissionState: admitTransition(ctx, ctx.admission.source({
|
|
145
|
+
kind: "refs",
|
|
146
|
+
result: validation.result,
|
|
147
|
+
refs: validation.query.kind === "refs" ? validation.query.refs : query.refs,
|
|
148
|
+
path
|
|
149
|
+
}))
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
case "filter": {
|
|
153
|
+
const input = validateQueryNode(query.input, `${path}.input`, ctx);
|
|
154
|
+
const predicate = validatePredicate(query.predicate, input.result, input.admissionState, `${path}.predicate`, ctx);
|
|
155
|
+
return {
|
|
156
|
+
result: input.result,
|
|
157
|
+
query: { ...query, input: input.query, predicate },
|
|
158
|
+
admissionState: input.admissionState
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
case "text": {
|
|
162
|
+
const input = validateQueryNode(query.input, `${path}.input`, ctx);
|
|
163
|
+
const textFields = validateTextQuery(query.query, query.fields, input.result, input.admissionState, path, ctx);
|
|
164
|
+
return {
|
|
165
|
+
result: input.result,
|
|
166
|
+
query: {
|
|
167
|
+
...query,
|
|
168
|
+
input: input.query,
|
|
169
|
+
fields: query.fields,
|
|
170
|
+
fieldsByObjectType: query.fields ? undefined : textFields.fieldsByObjectType
|
|
171
|
+
},
|
|
172
|
+
admissionState: input.admissionState
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
case "vector": {
|
|
176
|
+
const input = validateQueryNode(query.input, `${path}.input`, ctx);
|
|
177
|
+
return {
|
|
178
|
+
result: input.result,
|
|
179
|
+
query: validateVectorProfileQuery({ ...query, input: input.query }, input.result, input.admissionState, path, ctx),
|
|
180
|
+
admissionState: input.admissionState
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
case "traverse": {
|
|
184
|
+
const input = validateQueryNode(query.input, `${path}.input`, ctx);
|
|
185
|
+
const result = validateTraverse(query.linkId, query.direction, query.sourceObjectTypeId, input.result, path, ctx);
|
|
186
|
+
return {
|
|
187
|
+
result,
|
|
188
|
+
query: { ...query, input: input.query },
|
|
189
|
+
admissionState: admitTransition(ctx, ctx.admission.edge({
|
|
190
|
+
state: input.admissionState,
|
|
191
|
+
result,
|
|
192
|
+
linkId: query.linkId,
|
|
193
|
+
direction: query.direction,
|
|
194
|
+
sourceObjectTypeId: query.sourceObjectTypeId,
|
|
195
|
+
use: "traverse",
|
|
196
|
+
path
|
|
197
|
+
}))
|
|
198
|
+
};
|
|
199
|
+
}
|
|
222
200
|
case "set":
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
query.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
201
|
+
return validateSet(query.inputs, query.op, path, ctx);
|
|
202
|
+
case "sort": {
|
|
203
|
+
const input = validateQueryNode(query.input, `${path}.input`, ctx);
|
|
204
|
+
const fields = validateSortFields(query.fields, input.result, input.admissionState, "sort", path, ctx);
|
|
205
|
+
return {
|
|
206
|
+
result: input.result,
|
|
207
|
+
query: { ...query, input: input.query, fields },
|
|
208
|
+
admissionState: input.admissionState
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
case "limit": {
|
|
212
|
+
const input = validateQueryNode(query.input, `${path}.input`, ctx);
|
|
213
|
+
validateLimit(query.limit, path, ctx);
|
|
214
|
+
return {
|
|
215
|
+
result: input.result,
|
|
216
|
+
query: { ...query, input: input.query },
|
|
217
|
+
admissionState: input.admissionState
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
case "page": {
|
|
221
|
+
const input = validateQueryNode(query.input, `${path}.input`, ctx);
|
|
222
|
+
validatePage(query.pageSize, query.pageToken, path, ctx);
|
|
223
|
+
return {
|
|
224
|
+
result: input.result,
|
|
225
|
+
query: { ...query, input: input.query },
|
|
226
|
+
admissionState: input.admissionState
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
case "project": {
|
|
230
|
+
const input = validateQueryNode(query.input, `${path}.input`, ctx);
|
|
231
|
+
validateProjection(query.properties, input.result, input.admissionState, path, ctx);
|
|
232
|
+
return {
|
|
233
|
+
result: input.result,
|
|
234
|
+
query: { ...query, input: input.query },
|
|
235
|
+
admissionState: input.admissionState
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
case "expand": {
|
|
239
|
+
const input = validateQueryNode(query.input, `${path}.input`, ctx);
|
|
240
|
+
const expansions = validateExpansions(query.expansions, input.result, input.admissionState, `${path}.expansions`, ctx);
|
|
241
|
+
return {
|
|
242
|
+
result: input.result,
|
|
243
|
+
query: { ...query, input: input.query, expansions },
|
|
244
|
+
admissionState: input.admissionState
|
|
245
|
+
};
|
|
246
|
+
}
|
|
242
247
|
}
|
|
243
248
|
}
|
|
244
|
-
function
|
|
245
|
-
if (
|
|
246
|
-
addIssue(
|
|
249
|
+
function validateRefs(refs, path, ctx) {
|
|
250
|
+
if (refs.length === 0) {
|
|
251
|
+
addIssue(ctx, `${path}.refs`, "empty_refs", "refs must include at least one object reference");
|
|
247
252
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
+
if (refs.length > ctx.maxRefs) {
|
|
254
|
+
addIssue(ctx, `${path}.refs`, "too_many_refs", `refs must include at most ${ctx.maxRefs} object references`);
|
|
255
|
+
}
|
|
256
|
+
const objectTypeIds = new Set;
|
|
257
|
+
refs.forEach((ref, index) => {
|
|
258
|
+
const refPath = `${path}.refs[${index}]`;
|
|
259
|
+
if (!ref.objectTypeId) {
|
|
260
|
+
addIssue(ctx, `${refPath}.objectTypeId`, "missing_object_type", "objectTypeId is required");
|
|
261
|
+
} else if (!ctx.ontology.getObjectTypeById(ref.objectTypeId)) {
|
|
262
|
+
addIssue(ctx, `${refPath}.objectTypeId`, "unknown_object_type", formatUnknownObjectTypeMessage(ref.objectTypeId));
|
|
263
|
+
} else {
|
|
264
|
+
objectTypeIds.add(ref.objectTypeId);
|
|
265
|
+
}
|
|
266
|
+
if (!ref.primaryId) {
|
|
267
|
+
addIssue(ctx, `${refPath}.primaryId`, "missing_primary_id", "primaryId is required");
|
|
268
|
+
}
|
|
253
269
|
});
|
|
270
|
+
return {
|
|
271
|
+
result: { objectTypeIds: [...objectTypeIds] },
|
|
272
|
+
query: { kind: "refs", refs: refs.map((ref) => ({ ...ref })) }
|
|
273
|
+
};
|
|
254
274
|
}
|
|
255
|
-
function
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
275
|
+
function validateStart(objectTypeId, includeSubtypes, path, ctx) {
|
|
276
|
+
if (!objectTypeId) {
|
|
277
|
+
addIssue(ctx, path, "missing_object_type", "start.objectTypeId is required");
|
|
278
|
+
return { result: { objectTypeIds: [] }, query: { kind: "start", objectTypeId } };
|
|
259
279
|
}
|
|
260
|
-
if (
|
|
261
|
-
addIssue(
|
|
280
|
+
if (!ctx.ontology.getObjectTypeById(objectTypeId)) {
|
|
281
|
+
addIssue(ctx, path, "unknown_object_type", formatUnknownObjectTypeMessage(objectTypeId));
|
|
282
|
+
return { result: { objectTypeIds: [] }, query: { kind: "start", objectTypeId } };
|
|
262
283
|
}
|
|
284
|
+
const objectTypeIds = includeSubtypes ? uniqueStrings([objectTypeId, ...ctx.ontology.listSubTypes(objectTypeId)]) : [objectTypeId];
|
|
285
|
+
return { result: { objectTypeIds }, query: { kind: "start", objectTypeId, includeSubtypes } };
|
|
286
|
+
}
|
|
287
|
+
function validatePredicate(predicate, shape, admissionState, path, ctx) {
|
|
263
288
|
switch (predicate.op) {
|
|
264
289
|
case "and":
|
|
265
290
|
case "or":
|
|
266
|
-
predicate.items.
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
return
|
|
291
|
+
if (predicate.items.length === 0) {
|
|
292
|
+
addIssue(ctx, path, "empty_predicate_group", `Predicate '${predicate.op}' must not be empty`);
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
...predicate,
|
|
296
|
+
items: predicate.items.map((item, index) => validatePredicate(item, shape, admissionState, `${path}.items[${index}]`, ctx))
|
|
297
|
+
};
|
|
270
298
|
case "not":
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
function predicateScalarOperation(predicate) {
|
|
278
|
-
switch (predicate.op) {
|
|
299
|
+
return {
|
|
300
|
+
...predicate,
|
|
301
|
+
item: validatePredicate(predicate.item, shape, admissionState, `${path}.item`, ctx)
|
|
302
|
+
};
|
|
279
303
|
case "eq":
|
|
280
304
|
case "neq":
|
|
281
|
-
case "in":
|
|
282
|
-
case "contains":
|
|
283
|
-
return "equality";
|
|
284
305
|
case "lt":
|
|
285
306
|
case "lte":
|
|
286
307
|
case "gt":
|
|
287
|
-
case "gte":
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
308
|
+
case "gte": {
|
|
309
|
+
const { scalarKind: _authoredScalarKind, ...authoredPredicate } = predicate;
|
|
310
|
+
const scalarKind = resolveAndValidatePredicateProperty(predicate.propertyId, predicate.op, shape, path, ctx);
|
|
311
|
+
validatePredicateValue(predicate.propertyId, predicate.value, shape, path, ctx);
|
|
312
|
+
admitProperty(ctx, {
|
|
313
|
+
state: admissionState,
|
|
314
|
+
propertyId: predicate.propertyId,
|
|
315
|
+
use: "filter",
|
|
316
|
+
path
|
|
317
|
+
});
|
|
318
|
+
return {
|
|
319
|
+
...authoredPredicate,
|
|
320
|
+
value: normalizeObjectQueryValue(predicate.value, scalarKind),
|
|
321
|
+
...scalarKind ? { scalarKind } : {}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
case "in": {
|
|
325
|
+
const { scalarKind: _authoredScalarKind, ...authoredPredicate } = predicate;
|
|
326
|
+
const scalarKind = resolveAndValidatePredicateProperty(predicate.propertyId, predicate.op, shape, path, ctx);
|
|
327
|
+
if (predicate.values.length === 0) {
|
|
328
|
+
addIssue(ctx, path, "empty_in_values", "Predicate 'in' must include at least one value");
|
|
329
|
+
}
|
|
330
|
+
predicate.values.forEach((value, index) => {
|
|
331
|
+
validatePredicateValue(predicate.propertyId, value, shape, `${path}.values[${index}]`, ctx);
|
|
332
|
+
});
|
|
333
|
+
admitProperty(ctx, {
|
|
334
|
+
state: admissionState,
|
|
335
|
+
propertyId: predicate.propertyId,
|
|
336
|
+
use: "filter",
|
|
337
|
+
path
|
|
338
|
+
});
|
|
339
|
+
return {
|
|
340
|
+
...authoredPredicate,
|
|
341
|
+
values: predicate.values.map((value) => normalizeObjectQueryValue(value, scalarKind)),
|
|
342
|
+
...scalarKind ? { scalarKind } : {}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
case "exists":
|
|
346
|
+
resolveAndValidatePredicateProperty(predicate.propertyId, predicate.op, shape, path, ctx);
|
|
347
|
+
admitProperty(ctx, {
|
|
348
|
+
state: admissionState,
|
|
349
|
+
propertyId: predicate.propertyId,
|
|
350
|
+
use: "filter",
|
|
351
|
+
path
|
|
352
|
+
});
|
|
353
|
+
if (typeof predicate.value !== "boolean") {
|
|
354
|
+
addIssue(ctx, path, "invalid_exists_value", "Predicate 'exists' value must be boolean");
|
|
355
|
+
}
|
|
356
|
+
return predicate;
|
|
357
|
+
case "contains": {
|
|
358
|
+
const { scalarKind: _authoredScalarKind, ...authoredPredicate } = predicate;
|
|
359
|
+
const scalarKind = resolveAndValidatePredicateProperty(predicate.propertyId, predicate.op, shape, path, ctx);
|
|
360
|
+
validateContainsValue(predicate.propertyId, predicate.value, shape, path, ctx);
|
|
361
|
+
admitProperty(ctx, {
|
|
362
|
+
state: admissionState,
|
|
363
|
+
propertyId: predicate.propertyId,
|
|
364
|
+
use: "filter",
|
|
365
|
+
path
|
|
366
|
+
});
|
|
367
|
+
return {
|
|
368
|
+
...authoredPredicate,
|
|
369
|
+
value: normalizeObjectQueryValue(predicate.value, scalarKind),
|
|
370
|
+
...scalarKind ? { scalarKind } : {}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
296
373
|
}
|
|
297
|
-
if (capabilities.sortKinds?.[field.kind] === true)
|
|
298
|
-
return;
|
|
299
|
-
addIssue(issues, path, "sort_kind_not_supported", `Provider does not support sort kind '${field.kind}'`);
|
|
300
374
|
}
|
|
301
|
-
function
|
|
302
|
-
const
|
|
303
|
-
if (
|
|
304
|
-
return;
|
|
305
|
-
if (!Number.isInteger(maxLimit) || maxLimit < 0) {
|
|
306
|
-
addIssue(issues, path, "invalid_provider_limit_capability", "Provider limit capability maxLimit must be a non-negative integer");
|
|
375
|
+
function resolveAndValidatePredicateProperty(propertyId, op, shape, path, ctx) {
|
|
376
|
+
const properties = getPropertiesForResult(propertyId, shape, path, ctx);
|
|
377
|
+
if (properties.length === 0)
|
|
307
378
|
return;
|
|
379
|
+
const schemas = [];
|
|
380
|
+
for (const { objectType, property } of properties) {
|
|
381
|
+
const schema = resolveSchemaForProperty(property, objectType.id, ctx, path);
|
|
382
|
+
if (schema)
|
|
383
|
+
schemas.push(schema);
|
|
384
|
+
if (isPrimaryExactPredicate(property, op)) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (property.query?.searchable !== true || property.query.filterable !== true) {
|
|
388
|
+
addIssue(ctx, path, "property_not_filterable", `Property '${propertyId}' on '${objectType.id}' must set query.searchable: true and query.filterable: true before it can be used in predicates`);
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (!schema)
|
|
392
|
+
continue;
|
|
393
|
+
if ((op === "lt" || op === "lte" || op === "gt" || op === "gte") && !isSortableSchema(schema)) {
|
|
394
|
+
addIssue(ctx, path, "operator_not_supported_for_schema", `Predicate '${op}' cannot be used with property '${propertyId}' on '${objectType.id}' because its schema is not orderable`);
|
|
395
|
+
}
|
|
396
|
+
if ((op === "eq" || op === "neq" || op === "in" || op === "exists") && !isExactSchema(schema)) {
|
|
397
|
+
addIssue(ctx, path, "operator_not_supported_for_schema", `Predicate '${op}' cannot be used with property '${propertyId}' on '${objectType.id}' because its schema cannot be exact-matched`);
|
|
398
|
+
}
|
|
399
|
+
if (op === "contains" && !isContainsSchema(schema)) {
|
|
400
|
+
addIssue(ctx, path, "operator_not_supported_for_schema", `Predicate 'contains' cannot be used with property '${propertyId}' on '${objectType.id}' because its schema is not string, array, or map`);
|
|
401
|
+
}
|
|
308
402
|
}
|
|
309
|
-
if (
|
|
310
|
-
addIssue(issues, path, "provider_limit_too_large", `Provider supports limit up to ${maxLimit}`);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
function collectProviderMaxPageSizeIssues(pageSize, path, capabilities, issues) {
|
|
314
|
-
const maxPageSize = capabilities.limits?.maxPageSize;
|
|
315
|
-
if (maxPageSize === undefined)
|
|
316
|
-
return;
|
|
317
|
-
if (!Number.isInteger(maxPageSize) || maxPageSize <= 0) {
|
|
318
|
-
addIssue(issues, path, "invalid_provider_page_size_capability", "Provider limit capability maxPageSize must be a positive integer");
|
|
403
|
+
if (op === "exists")
|
|
319
404
|
return;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
function collectFallbackIssues(query, options) {
|
|
326
|
-
const issues = [];
|
|
327
|
-
if (options.allowFallback === false) {
|
|
328
|
-
addIssue(issues, "$", "fallback_disabled", "Object query fallback is disabled");
|
|
329
|
-
return issues;
|
|
330
|
-
}
|
|
331
|
-
const maxFallbackRows = options.maxFallbackRows ?? DEFAULT_MAX_FALLBACK_ROWS;
|
|
332
|
-
if (!Number.isInteger(maxFallbackRows) || maxFallbackRows <= 0) {
|
|
333
|
-
addIssue(issues, "$", "invalid_fallback_bound", "maxFallbackRows must be a positive integer");
|
|
334
|
-
}
|
|
335
|
-
if (options.requiresExplicitFallbackBound !== false && !hasExplicitResultBound(query)) {
|
|
336
|
-
addIssue(issues, "$", "fallback_requires_bound", "Fallback requires an explicit limit or page node");
|
|
337
|
-
}
|
|
338
|
-
collectFallbackNodeIssues(query, "$", issues);
|
|
339
|
-
return issues;
|
|
405
|
+
if (op === "contains")
|
|
406
|
+
return resolveArrayItemScalarKind(schemas, ctx);
|
|
407
|
+
return resolveCommonQueryScalarKind(schemas, propertyId, path, ctx);
|
|
340
408
|
}
|
|
341
|
-
function
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
switch (query.kind) {
|
|
346
|
-
case "start":
|
|
347
|
-
case "refs":
|
|
348
|
-
return;
|
|
349
|
-
case "filter":
|
|
350
|
-
case "limit":
|
|
351
|
-
case "page":
|
|
352
|
-
case "project":
|
|
353
|
-
case "expand":
|
|
354
|
-
collectFallbackNodeIssues(query.input, `${path}.input`, issues);
|
|
355
|
-
return;
|
|
356
|
-
case "sort":
|
|
357
|
-
query.fields.forEach((field, index) => {
|
|
358
|
-
if (field.kind === "relevance") {
|
|
359
|
-
addIssue(issues, `${path}.fields[${index}]`, "fallback_sort_kind_not_supported", "Fallback does not support relevance sorting");
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
collectFallbackNodeIssues(query.input, `${path}.input`, issues);
|
|
363
|
-
return;
|
|
364
|
-
case "text":
|
|
365
|
-
case "vector":
|
|
366
|
-
case "traverse":
|
|
367
|
-
collectFallbackNodeIssues(query.input, `${path}.input`, issues);
|
|
368
|
-
return;
|
|
369
|
-
case "set":
|
|
370
|
-
query.inputs.forEach((input, index) => {
|
|
371
|
-
collectFallbackNodeIssues(input, `${path}.inputs[${index}]`, issues);
|
|
372
|
-
});
|
|
409
|
+
function resolveArrayItemScalarKind(schemas, ctx) {
|
|
410
|
+
const kinds = new Set;
|
|
411
|
+
for (const schema of schemas) {
|
|
412
|
+
if (typeof schema === "string" || schema.type !== "array")
|
|
373
413
|
return;
|
|
414
|
+
const items = resolveValueTypeRefs(schema.items, ctx.valueTypesById);
|
|
415
|
+
kinds.add(items ? queryScalarKindForSchema(items) : undefined);
|
|
374
416
|
}
|
|
417
|
+
return kinds.size === 1 ? [...kinds][0] : undefined;
|
|
375
418
|
}
|
|
376
|
-
function
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
case "refs":
|
|
381
|
-
return true;
|
|
382
|
-
case "start":
|
|
383
|
-
return false;
|
|
384
|
-
case "set":
|
|
385
|
-
return query.inputs.some(hasExplicitResultBound);
|
|
386
|
-
default:
|
|
387
|
-
return hasExplicitResultBound(query.input);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
function addIssue(issues, path, code, message) {
|
|
391
|
-
issues.push({ path, code, message });
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// src/objects/query/validate.ts
|
|
395
|
-
var DEFAULT_MAX_LIMIT = 1000;
|
|
396
|
-
var DEFAULT_MAX_PAGE_SIZE = 1000;
|
|
397
|
-
var DEFAULT_MAX_REFS = 1000;
|
|
398
|
-
var NOOP_ADMISSION_STATE = Object.freeze({});
|
|
399
|
-
var NOOP_QUERY_ADMISSION = Object.freeze({
|
|
400
|
-
empty: () => NOOP_ADMISSION_STATE,
|
|
401
|
-
source: () => ({ state: NOOP_ADMISSION_STATE }),
|
|
402
|
-
property: () => {
|
|
419
|
+
function resolveValueTypeRefs(schema, valueTypesById, seen = new Set) {
|
|
420
|
+
if (typeof schema === "string" || schema.type !== "valueTypeRef")
|
|
421
|
+
return schema;
|
|
422
|
+
if (seen.has(schema.valueTypeId))
|
|
403
423
|
return;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}),
|
|
408
|
-
set: (input) => input.states[0] ?? NOOP_ADMISSION_STATE
|
|
409
|
-
});
|
|
410
|
-
function validateObjectQuery2(query, options) {
|
|
411
|
-
const admitted = validateObjectQueryWithAdmission(query, options, NOOP_QUERY_ADMISSION);
|
|
412
|
-
return {
|
|
413
|
-
query: admitted.query,
|
|
414
|
-
result: admitted.result,
|
|
415
|
-
touchedObjectTypeIds: admitted.touchedObjectTypeIds
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
function validateObjectQueryWithAdmission(query, options, admission) {
|
|
419
|
-
const normalized = options.normalize === false ? query : normalizeObjectQuery2(query);
|
|
420
|
-
const ctx = createValidationContext(options, admission);
|
|
421
|
-
validateVectorComposition(query, ctx);
|
|
422
|
-
const validation = validateQueryNode(normalized, "$", ctx);
|
|
423
|
-
if (ctx.issues.length > 0) {
|
|
424
|
-
throw new ObjectQueryValidationError2(ctx.issues);
|
|
425
|
-
}
|
|
426
|
-
const denial = ctx.admissionDenials[0];
|
|
427
|
-
if (denial)
|
|
428
|
-
throw denial;
|
|
429
|
-
return {
|
|
430
|
-
query: validation.query,
|
|
431
|
-
result: validation.result,
|
|
432
|
-
touchedObjectTypeIds: [...ctx.touchedObjectTypeIds],
|
|
433
|
-
admissionState: validation.admissionState
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
function collectObjectQueryValidationIssues2(query, options) {
|
|
437
|
-
const normalized = options.normalize === false ? query : normalizeObjectQuery2(query);
|
|
438
|
-
const ctx = createValidationContext(options, NOOP_QUERY_ADMISSION);
|
|
439
|
-
validateVectorComposition(query, ctx);
|
|
440
|
-
validateQueryNode(normalized, "$", ctx);
|
|
441
|
-
return ctx.issues;
|
|
442
|
-
}
|
|
443
|
-
function resolveObjectQueryResultShape2(query, options) {
|
|
444
|
-
return validateObjectQuery2(query, options).result;
|
|
424
|
+
seen.add(schema.valueTypeId);
|
|
425
|
+
const resolved = schema._resolved ?? valueTypesById.get(schema.valueTypeId)?.schema;
|
|
426
|
+
return resolved ? resolveValueTypeRefs(resolved, valueTypesById, seen) : undefined;
|
|
445
427
|
}
|
|
446
|
-
function
|
|
447
|
-
if (
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
428
|
+
function normalizeObjectQueryValue(value, scalarKind) {
|
|
429
|
+
if (scalarKind !== "decimal" || typeof value !== "string")
|
|
430
|
+
return value;
|
|
431
|
+
try {
|
|
432
|
+
return normalizeDecimalValue2(value);
|
|
433
|
+
} catch {
|
|
434
|
+
return value;
|
|
435
|
+
}
|
|
453
436
|
}
|
|
454
|
-
function
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
admissionDenials: []
|
|
465
|
-
};
|
|
437
|
+
function resolveCommonQueryScalarKind(schemas, propertyId, path, ctx) {
|
|
438
|
+
const scalarKinds = schemas.map(queryScalarKindForSchema);
|
|
439
|
+
if (scalarKinds.length === 0 || scalarKinds.some((kind) => kind === undefined)) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const resolvedKinds = new Set(scalarKinds);
|
|
443
|
+
if (resolvedKinds.size === 1)
|
|
444
|
+
return scalarKinds[0];
|
|
445
|
+
addIssue(ctx, path, "incompatible_property_scalar_kinds", `Property '${propertyId}' resolves to incompatible scalar schemas: ${[...resolvedKinds].join(", ")}`);
|
|
446
|
+
return;
|
|
466
447
|
}
|
|
467
|
-
function
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
448
|
+
function validatePredicateValue(propertyId, value, shape, path, ctx) {
|
|
449
|
+
for (const { objectType, property } of getPropertiesForResult(propertyId, shape, path, ctx, {
|
|
450
|
+
reportMissing: false
|
|
451
|
+
})) {
|
|
452
|
+
try {
|
|
453
|
+
validatePropertyValue2(property, value, `${objectType.id}.${property.id}`, ctx.valueTypesById);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
addIssue(ctx, path, "invalid_predicate_value", error instanceof Error ? error.message : `Invalid value for property '${property.id}' on '${objectType.id}'`);
|
|
456
|
+
}
|
|
471
457
|
}
|
|
472
|
-
return validation;
|
|
473
458
|
}
|
|
474
|
-
function
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
path
|
|
485
|
-
}
|
|
486
|
-
}
|
|
459
|
+
function validateContainsValue(propertyId, value, shape, path, ctx) {
|
|
460
|
+
for (const { objectType, property } of getPropertiesForResult(propertyId, shape, path, ctx, {
|
|
461
|
+
reportMissing: false
|
|
462
|
+
})) {
|
|
463
|
+
const schema = resolveSchemaForProperty(property, objectType.id, ctx, path);
|
|
464
|
+
if (!schema)
|
|
465
|
+
continue;
|
|
466
|
+
if (typeof schema === "string") {
|
|
467
|
+
if (isContainsSchema(schema)) {
|
|
468
|
+
if (typeof value !== "string") {
|
|
469
|
+
addIssue(ctx, path, "invalid_contains_value", `Predicate 'contains' value for '${objectType.id}.${property.id}' must be a string`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
continue;
|
|
487
473
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
refs: validation.query.kind === "refs" ? validation.query.refs : query.refs,
|
|
496
|
-
path
|
|
497
|
-
}))
|
|
498
|
-
};
|
|
474
|
+
if (schema.type === "array") {
|
|
475
|
+
try {
|
|
476
|
+
validateSchemaValue2(schema.items, value, `${objectType.id}.${property.id}[]`, ctx.valueTypesById);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
addIssue(ctx, path, "invalid_contains_value", error instanceof Error ? error.message : `Invalid contains value for '${objectType.id}.${property.id}'`);
|
|
479
|
+
}
|
|
480
|
+
continue;
|
|
499
481
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const predicate = validatePredicate(query.predicate, input.result, input.admissionState, `${path}.predicate`, ctx);
|
|
503
|
-
return {
|
|
504
|
-
result: input.result,
|
|
505
|
-
query: { ...query, input: input.query, predicate },
|
|
506
|
-
admissionState: input.admissionState
|
|
507
|
-
};
|
|
482
|
+
if (schema.type === "map" && typeof value !== "string") {
|
|
483
|
+
addIssue(ctx, path, "invalid_contains_value", `Predicate 'contains' value for '${objectType.id}.${property.id}' must be a map key string`);
|
|
508
484
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
};
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function validateTextQuery(query, fields, shape, admissionState, path, ctx) {
|
|
488
|
+
if (query.trim().length === 0) {
|
|
489
|
+
addIssue(ctx, path, "empty_text_query", "Text query must not be empty");
|
|
490
|
+
}
|
|
491
|
+
const fieldsByObjectType = fields ? undefined : {};
|
|
492
|
+
for (const objectType of getObjectTypesForResult(shape, ctx)) {
|
|
493
|
+
const fieldIds = fields ?? objectType.search?.defaultText;
|
|
494
|
+
if (!fieldIds || fieldIds.length === 0) {
|
|
495
|
+
addIssue(ctx, path, "missing_text_fields", `Object type '${objectType.id}' must define search.defaultText or the text query must specify fields`);
|
|
496
|
+
continue;
|
|
522
497
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
498
|
+
if (fieldsByObjectType)
|
|
499
|
+
fieldsByObjectType[objectType.id] = uniqueStrings(fieldIds);
|
|
500
|
+
for (const fieldId of fieldIds) {
|
|
501
|
+
admitProperty(ctx, {
|
|
502
|
+
state: admissionState,
|
|
503
|
+
propertyId: fieldId,
|
|
504
|
+
objectTypeId: objectType.id,
|
|
505
|
+
use: "text",
|
|
506
|
+
path: `${path}.fields`
|
|
507
|
+
});
|
|
508
|
+
const property = getProperty(objectType, fieldId);
|
|
509
|
+
if (!property) {
|
|
510
|
+
addIssue(ctx, path, "unknown_text_field", `Text query references unknown property '${fieldId}' on '${objectType.id}'`);
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
assertStaticSearchProperty(objectType.id, property, "text query", path, ctx);
|
|
514
|
+
assertQueryFlag(objectType.id, property, "text", "text query", path, ctx);
|
|
515
|
+
const schema = resolveSchemaForProperty(property, objectType.id, ctx, path);
|
|
516
|
+
if (schema && !isTextSchema(schema)) {
|
|
517
|
+
addIssue(ctx, path, "text_field_not_string_like", `Text query field '${fieldId}' on '${objectType.id}' must be string-like`);
|
|
518
|
+
}
|
|
530
519
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
520
|
+
}
|
|
521
|
+
return fieldsByObjectType ? { fieldsByObjectType } : {};
|
|
522
|
+
}
|
|
523
|
+
function validateVectorProfileQuery(query, shape, state, path, ctx) {
|
|
524
|
+
const types = getObjectTypesForResult(shape, ctx);
|
|
525
|
+
if (types.length !== 1 || !Number.isSafeInteger(query.k) || query.k < 1 || query.k > MAX_VECTOR_K) {
|
|
526
|
+
addIssue(ctx, path, "invalid_vector_profile_query", `Vector search requires one type, one profile and k between 1 and ${MAX_VECTOR_K}`);
|
|
527
|
+
return query;
|
|
528
|
+
}
|
|
529
|
+
const objectType = types[0];
|
|
530
|
+
const profile = query.profile === undefined ? undefined : objectType.search?.vectors?.[query.profile];
|
|
531
|
+
if (!profile) {
|
|
532
|
+
addIssue(ctx, path, "unknown_vector_profile", `Unknown vector profile: ${query.profile}`);
|
|
533
|
+
return query;
|
|
534
|
+
}
|
|
535
|
+
for (const propertyId of profile.source)
|
|
536
|
+
admitProperty(ctx, { state, propertyId, objectTypeId: objectType.id, use: "vector", path });
|
|
537
|
+
let values = query.vector;
|
|
538
|
+
try {
|
|
539
|
+
if (typeof values === "string") {
|
|
540
|
+
if (!values.trim() || values.length > 8000) {
|
|
541
|
+
throw new Error("Vector search text must be nonempty and at most 8000 characters.");
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
values = normalizeVector2(values, profile.model.definition.dimensions);
|
|
547
545
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
546
|
+
} catch (error) {
|
|
547
|
+
addIssue(ctx, path, "invalid_vector", error instanceof Error ? error.message : "Invalid vector");
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
...query,
|
|
551
|
+
vector: values,
|
|
552
|
+
source: [...profile.source],
|
|
553
|
+
configuration: vectorConfiguration(profile)
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function validateTraverse(linkId, direction, sourceObjectTypeId, shape, path, ctx) {
|
|
557
|
+
if (!linkId) {
|
|
558
|
+
addIssue(ctx, path, "missing_link_id", "traverse.linkId is required");
|
|
559
|
+
return { objectTypeIds: [] };
|
|
560
|
+
}
|
|
561
|
+
if (direction === "outgoing") {
|
|
562
|
+
if (sourceObjectTypeId !== undefined) {
|
|
563
|
+
addIssue(ctx, path, "traverse_source_not_applicable", "traverse.sourceObjectTypeId only applies to incoming traversal");
|
|
558
564
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
565
|
+
return validateOutgoingTraverse(linkId, shape, path, ctx);
|
|
566
|
+
}
|
|
567
|
+
return validateIncomingTraverse(linkId, sourceObjectTypeId, shape, path, ctx);
|
|
568
|
+
}
|
|
569
|
+
function validateOutgoingTraverse(linkId, shape, path, ctx) {
|
|
570
|
+
const targetTypeIds = [];
|
|
571
|
+
for (const objectType of getObjectTypesForResult(shape, ctx)) {
|
|
572
|
+
const link = objectType.links.find((candidate) => candidate.id === linkId);
|
|
573
|
+
if (!link) {
|
|
574
|
+
addIssue(ctx, path, "unknown_link", `Outgoing traversal references unknown link '${linkId}' on '${objectType.id}'`);
|
|
575
|
+
continue;
|
|
567
576
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
return {
|
|
572
|
-
result: input.result,
|
|
573
|
-
query: { ...query, input: input.query },
|
|
574
|
-
admissionState: input.admissionState
|
|
575
|
-
};
|
|
577
|
+
if (link.targetObjectTypeId === "*") {
|
|
578
|
+
addIssue(ctx, path, "wildcard_traverse_target", `Outgoing traversal over wildcard link '${objectType.id}.${linkId}' cannot infer a target object type`);
|
|
579
|
+
continue;
|
|
576
580
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
};
|
|
581
|
+
const declaredTargets = Array.isArray(link.targetObjectTypeId) ? link.targetObjectTypeId : [link.targetObjectTypeId];
|
|
582
|
+
for (const targetId of declaredTargets) {
|
|
583
|
+
if (!ctx.ontology.getObjectTypeById(targetId)) {
|
|
584
|
+
addIssue(ctx, path, "unknown_link_target", `Outgoing traversal over '${objectType.id}.${linkId}' targets unknown object type '${targetId}'`);
|
|
585
|
+
} else {
|
|
586
|
+
targetTypeIds.push(targetId);
|
|
587
|
+
}
|
|
585
588
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
589
|
+
}
|
|
590
|
+
return { objectTypeIds: uniqueStrings(targetTypeIds) };
|
|
591
|
+
}
|
|
592
|
+
function validateIncomingTraverse(linkId, sourceObjectTypeId, shape, path, ctx) {
|
|
593
|
+
let candidateTypes = ctx.ontology.listObjectTypes();
|
|
594
|
+
if (sourceObjectTypeId !== undefined) {
|
|
595
|
+
const sourceType = ctx.ontology.getObjectTypeById(sourceObjectTypeId);
|
|
596
|
+
if (!sourceType) {
|
|
597
|
+
addIssue(ctx, path, "unknown_traverse_source", `Incoming traversal references unknown source object type '${sourceObjectTypeId}'`);
|
|
598
|
+
return { objectTypeIds: [] };
|
|
594
599
|
}
|
|
600
|
+
candidateTypes = [sourceType];
|
|
595
601
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
602
|
+
const sourceTypeIds = [];
|
|
603
|
+
for (const sourceType of candidateTypes) {
|
|
604
|
+
const link = sourceType.links.find((candidate) => candidate.id === linkId);
|
|
605
|
+
if (!link)
|
|
606
|
+
continue;
|
|
607
|
+
if (link.targetObjectTypeId === "*") {
|
|
608
|
+
sourceTypeIds.push(sourceType.id);
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const matches = shape.objectTypeIds.some((targetTypeId) => ctx.ontology.isValidLinkTarget(link.targetObjectTypeId, targetTypeId));
|
|
612
|
+
if (matches)
|
|
613
|
+
sourceTypeIds.push(sourceType.id);
|
|
600
614
|
}
|
|
601
|
-
if (
|
|
602
|
-
|
|
615
|
+
if (sourceTypeIds.length === 0) {
|
|
616
|
+
addIssue(ctx, path, "incoming_link_not_found", `Incoming traversal found no source object types with link '${linkId}' targeting ${shape.objectTypeIds.join(" | ")}`);
|
|
603
617
|
}
|
|
604
|
-
|
|
605
|
-
refs.forEach((ref, index) => {
|
|
606
|
-
const refPath = `${path}.refs[${index}]`;
|
|
607
|
-
if (!ref.objectTypeId) {
|
|
608
|
-
addIssue2(ctx, `${refPath}.objectTypeId`, "missing_object_type", "objectTypeId is required");
|
|
609
|
-
} else if (!ctx.ontology.getObjectTypeById(ref.objectTypeId)) {
|
|
610
|
-
addIssue2(ctx, `${refPath}.objectTypeId`, "unknown_object_type", formatUnknownObjectTypeMessage(ref.objectTypeId));
|
|
611
|
-
} else {
|
|
612
|
-
objectTypeIds.add(ref.objectTypeId);
|
|
613
|
-
}
|
|
614
|
-
if (!ref.primaryId) {
|
|
615
|
-
addIssue2(ctx, `${refPath}.primaryId`, "missing_primary_id", "primaryId is required");
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
return {
|
|
619
|
-
result: { objectTypeIds: [...objectTypeIds] },
|
|
620
|
-
query: { kind: "refs", refs: refs.map((ref) => ({ ...ref })) }
|
|
621
|
-
};
|
|
618
|
+
return { objectTypeIds: uniqueStrings(sourceTypeIds) };
|
|
622
619
|
}
|
|
623
|
-
function
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
620
|
+
function validateExpansions(expansions, shape, admissionState, path, ctx) {
|
|
621
|
+
return expansions.map((expansion, index) => validateExpansion(expansion, shape, admissionState, `${path}[${index}]`, ctx));
|
|
622
|
+
}
|
|
623
|
+
function validateExpansion(expansion, shape, admissionState, path, ctx) {
|
|
624
|
+
if (!expansion.linkId) {
|
|
625
|
+
addIssue(ctx, path, "missing_expand_link", "expand.linkId is required");
|
|
626
|
+
return expansion;
|
|
627
627
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
628
|
+
const targetShape = expansion.direction === "incoming" ? resolveIncomingExpansionTargets(expansion.linkId, expansion.sourceObjectTypeId, shape, path, ctx) : resolveOutgoingExpansionTargets(expansion.linkId, expansion.sourceObjectTypeId, shape, path, ctx);
|
|
629
|
+
const targetAdmissionState = admitTransition(ctx, ctx.admission.edge({
|
|
630
|
+
state: admissionState,
|
|
631
|
+
result: targetShape,
|
|
632
|
+
linkId: expansion.linkId,
|
|
633
|
+
direction: expansion.direction,
|
|
634
|
+
sourceObjectTypeId: expansion.sourceObjectTypeId,
|
|
635
|
+
use: "expand",
|
|
636
|
+
path
|
|
637
|
+
}));
|
|
638
|
+
for (const targetTypeId of targetShape.objectTypeIds) {
|
|
639
|
+
ctx.touchedObjectTypeIds.add(targetTypeId);
|
|
631
640
|
}
|
|
632
|
-
|
|
633
|
-
|
|
641
|
+
validateExpansionLimit(expansion.limit, path, ctx);
|
|
642
|
+
const orderBy = expansion.orderBy ? validateSortFields(expansion.orderBy, targetShape, targetAdmissionState, "expand.orderBy", `${path}.orderBy`, ctx) : undefined;
|
|
643
|
+
const expand = expansion.expand ? validateExpansions(expansion.expand, targetShape, targetAdmissionState, `${path}.expand`, ctx) : undefined;
|
|
644
|
+
return { ...expansion, orderBy, expand };
|
|
634
645
|
}
|
|
635
|
-
function
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
item: validatePredicate(predicate.item, shape, admissionState, `${path}.item`, ctx)
|
|
650
|
-
};
|
|
651
|
-
case "eq":
|
|
652
|
-
case "neq":
|
|
653
|
-
case "lt":
|
|
654
|
-
case "lte":
|
|
655
|
-
case "gt":
|
|
656
|
-
case "gte": {
|
|
657
|
-
const { scalarKind: _authoredScalarKind, ...authoredPredicate } = predicate;
|
|
658
|
-
const scalarKind = resolveAndValidatePredicateProperty(predicate.propertyId, predicate.op, shape, path, ctx);
|
|
659
|
-
validatePredicateValue(predicate.propertyId, predicate.value, shape, path, ctx);
|
|
660
|
-
admitProperty(ctx, {
|
|
661
|
-
state: admissionState,
|
|
662
|
-
propertyId: predicate.propertyId,
|
|
663
|
-
use: "filter",
|
|
664
|
-
path
|
|
665
|
-
});
|
|
666
|
-
return {
|
|
667
|
-
...authoredPredicate,
|
|
668
|
-
value: normalizeObjectQueryValue(predicate.value, scalarKind),
|
|
669
|
-
...scalarKind ? { scalarKind } : {}
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
case "in": {
|
|
673
|
-
const { scalarKind: _authoredScalarKind, ...authoredPredicate } = predicate;
|
|
674
|
-
const scalarKind = resolveAndValidatePredicateProperty(predicate.propertyId, predicate.op, shape, path, ctx);
|
|
675
|
-
if (predicate.values.length === 0) {
|
|
676
|
-
addIssue2(ctx, path, "empty_in_values", "Predicate 'in' must include at least one value");
|
|
677
|
-
}
|
|
678
|
-
predicate.values.forEach((value, index) => {
|
|
679
|
-
validatePredicateValue(predicate.propertyId, value, shape, `${path}.values[${index}]`, ctx);
|
|
680
|
-
});
|
|
681
|
-
admitProperty(ctx, {
|
|
682
|
-
state: admissionState,
|
|
683
|
-
propertyId: predicate.propertyId,
|
|
684
|
-
use: "filter",
|
|
685
|
-
path
|
|
686
|
-
});
|
|
687
|
-
return {
|
|
688
|
-
...authoredPredicate,
|
|
689
|
-
values: predicate.values.map((value) => normalizeObjectQueryValue(value, scalarKind)),
|
|
690
|
-
...scalarKind ? { scalarKind } : {}
|
|
691
|
-
};
|
|
646
|
+
function resolveOutgoingExpansionTargets(linkId, sourceObjectTypeId, shape, path, ctx) {
|
|
647
|
+
if (sourceObjectTypeId !== undefined) {
|
|
648
|
+
addIssue(ctx, path, "expand_source_not_applicable", "expand.sourceObjectTypeId only applies to incoming expansion");
|
|
649
|
+
}
|
|
650
|
+
const targetTypeIds = [];
|
|
651
|
+
let foundOnAnyType = false;
|
|
652
|
+
for (const objectType of getObjectTypesForResult(shape, ctx)) {
|
|
653
|
+
const link = objectType.links.find((candidate) => candidate.id === linkId);
|
|
654
|
+
if (!link)
|
|
655
|
+
continue;
|
|
656
|
+
foundOnAnyType = true;
|
|
657
|
+
if (link.targetObjectTypeId === "*") {
|
|
658
|
+
addIssue(ctx, path, "wildcard_expand_target", `Expansion over wildcard link '${objectType.id}.${linkId}' cannot infer a target object type`);
|
|
659
|
+
continue;
|
|
692
660
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
path
|
|
700
|
-
});
|
|
701
|
-
if (typeof predicate.value !== "boolean") {
|
|
702
|
-
addIssue2(ctx, path, "invalid_exists_value", "Predicate 'exists' value must be boolean");
|
|
661
|
+
const declaredTargets = Array.isArray(link.targetObjectTypeId) ? link.targetObjectTypeId : [link.targetObjectTypeId];
|
|
662
|
+
for (const targetId of declaredTargets) {
|
|
663
|
+
if (!ctx.ontology.getObjectTypeById(targetId)) {
|
|
664
|
+
addIssue(ctx, path, "unknown_expand_target", `Expansion over '${objectType.id}.${linkId}' targets unknown object type '${targetId}'`);
|
|
665
|
+
} else {
|
|
666
|
+
targetTypeIds.push(targetId);
|
|
703
667
|
}
|
|
704
|
-
return predicate;
|
|
705
|
-
case "contains": {
|
|
706
|
-
const { scalarKind: _authoredScalarKind, ...authoredPredicate } = predicate;
|
|
707
|
-
const scalarKind = resolveAndValidatePredicateProperty(predicate.propertyId, predicate.op, shape, path, ctx);
|
|
708
|
-
validateContainsValue(predicate.propertyId, predicate.value, shape, path, ctx);
|
|
709
|
-
admitProperty(ctx, {
|
|
710
|
-
state: admissionState,
|
|
711
|
-
propertyId: predicate.propertyId,
|
|
712
|
-
use: "filter",
|
|
713
|
-
path
|
|
714
|
-
});
|
|
715
|
-
return {
|
|
716
|
-
...authoredPredicate,
|
|
717
|
-
value: normalizeObjectQueryValue(predicate.value, scalarKind),
|
|
718
|
-
...scalarKind ? { scalarKind } : {}
|
|
719
|
-
};
|
|
720
668
|
}
|
|
721
669
|
}
|
|
670
|
+
if (!foundOnAnyType) {
|
|
671
|
+
addIssue(ctx, path, "unknown_expand_link", `Expansion references link '${linkId}' not present on result type ${shape.objectTypeIds.join(" | ")} (an expand abandoned by a later traverse?)`);
|
|
672
|
+
}
|
|
673
|
+
return { objectTypeIds: uniqueStrings(targetTypeIds) };
|
|
722
674
|
}
|
|
723
|
-
function
|
|
724
|
-
|
|
725
|
-
if (
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if (schema)
|
|
731
|
-
schemas.push(schema);
|
|
732
|
-
if (isPrimaryExactPredicate(property, op)) {
|
|
733
|
-
continue;
|
|
675
|
+
function resolveIncomingExpansionTargets(linkId, sourceObjectTypeId, shape, path, ctx) {
|
|
676
|
+
let candidateTypes = ctx.ontology.listObjectTypes();
|
|
677
|
+
if (sourceObjectTypeId !== undefined) {
|
|
678
|
+
const sourceType = ctx.ontology.getObjectTypeById(sourceObjectTypeId);
|
|
679
|
+
if (!sourceType) {
|
|
680
|
+
addIssue(ctx, path, "unknown_expand_source", `Incoming expansion references unknown source object type '${sourceObjectTypeId}'`);
|
|
681
|
+
return { objectTypeIds: [] };
|
|
734
682
|
}
|
|
735
|
-
|
|
736
|
-
|
|
683
|
+
candidateTypes = [sourceType];
|
|
684
|
+
}
|
|
685
|
+
const sourceTypeIds = [];
|
|
686
|
+
for (const sourceType of candidateTypes) {
|
|
687
|
+
const link = sourceType.links.find((candidate) => candidate.id === linkId);
|
|
688
|
+
if (!link)
|
|
737
689
|
continue;
|
|
738
|
-
|
|
739
|
-
|
|
690
|
+
if (link.targetObjectTypeId === "*") {
|
|
691
|
+
sourceTypeIds.push(sourceType.id);
|
|
740
692
|
continue;
|
|
741
|
-
if ((op === "lt" || op === "lte" || op === "gt" || op === "gte") && !isSortableSchema(schema)) {
|
|
742
|
-
addIssue2(ctx, path, "operator_not_supported_for_schema", `Predicate '${op}' cannot be used with property '${propertyId}' on '${objectType.id}' because its schema is not orderable`);
|
|
743
693
|
}
|
|
744
|
-
|
|
745
|
-
|
|
694
|
+
const matches = shape.objectTypeIds.some((targetTypeId) => ctx.ontology.isValidLinkTarget(link.targetObjectTypeId, targetTypeId));
|
|
695
|
+
if (matches)
|
|
696
|
+
sourceTypeIds.push(sourceType.id);
|
|
697
|
+
}
|
|
698
|
+
if (sourceTypeIds.length === 0) {
|
|
699
|
+
addIssue(ctx, path, "incoming_expand_link_not_found", `Incoming expansion found no source object types with link '${linkId}' targeting ${shape.objectTypeIds.join(" | ")}`);
|
|
700
|
+
}
|
|
701
|
+
return { objectTypeIds: uniqueStrings(sourceTypeIds) };
|
|
702
|
+
}
|
|
703
|
+
function validateExpansionLimit(limit, path, ctx) {
|
|
704
|
+
if (limit === undefined)
|
|
705
|
+
return;
|
|
706
|
+
if (!Number.isInteger(limit) || limit < 0) {
|
|
707
|
+
addIssue(ctx, `${path}.limit`, "invalid_expand_limit", "expand.limit must be a non-negative integer");
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function validateSet(inputs, op, path, ctx) {
|
|
711
|
+
if (inputs.length === 0) {
|
|
712
|
+
addIssue(ctx, path, "empty_set", `Set operation '${op}' must include at least one input`);
|
|
713
|
+
return {
|
|
714
|
+
result: { objectTypeIds: [] },
|
|
715
|
+
query: { kind: "set", op, inputs },
|
|
716
|
+
admissionState: ctx.admission.empty()
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
const results = inputs.map((input, index) => validateQueryNode(input, `${path}.inputs[${index}]`, ctx));
|
|
720
|
+
const first = results[0]?.result ?? { objectTypeIds: [] };
|
|
721
|
+
const expected = keyForTypeIds(first.objectTypeIds);
|
|
722
|
+
results.forEach((result, index) => {
|
|
723
|
+
if (keyForTypeIds(result.result.objectTypeIds) !== expected) {
|
|
724
|
+
addIssue(ctx, `${path}.inputs[${index}]`, "incompatible_set_input", `Set operation '${op}' inputs must produce the same object type set`);
|
|
746
725
|
}
|
|
747
|
-
|
|
748
|
-
|
|
726
|
+
});
|
|
727
|
+
return {
|
|
728
|
+
result: first,
|
|
729
|
+
query: { kind: "set", op, inputs: results.map((result) => result.query) },
|
|
730
|
+
admissionState: ctx.admission.set({
|
|
731
|
+
op,
|
|
732
|
+
states: results.map((result) => result.admissionState),
|
|
733
|
+
path
|
|
734
|
+
})
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
function validateSortFields(fields, shape, admissionState, use, path, ctx) {
|
|
738
|
+
if (fields.length === 0) {
|
|
739
|
+
addIssue(ctx, path, "empty_sort", "Sort must include at least one field");
|
|
740
|
+
return [];
|
|
741
|
+
}
|
|
742
|
+
const seen = new Set;
|
|
743
|
+
return fields.map((field, index) => {
|
|
744
|
+
if (field.direction && field.direction !== "asc" && field.direction !== "desc") {
|
|
745
|
+
addIssue(ctx, `${path}.fields[${index}]`, "invalid_sort_direction", "Sort direction must be asc or desc");
|
|
746
|
+
}
|
|
747
|
+
const key = field.kind === "property" ? `property:${field.propertyId}` : "relevance";
|
|
748
|
+
if (seen.has(key)) {
|
|
749
|
+
addIssue(ctx, `${path}.fields[${index}]`, "duplicate_sort_field", `Duplicate sort field '${key}'`);
|
|
750
|
+
}
|
|
751
|
+
seen.add(key);
|
|
752
|
+
if (field.kind === "relevance")
|
|
753
|
+
return field;
|
|
754
|
+
admitProperty(ctx, {
|
|
755
|
+
state: admissionState,
|
|
756
|
+
propertyId: field.propertyId,
|
|
757
|
+
use,
|
|
758
|
+
path: `${path}.fields[${index}]`
|
|
759
|
+
});
|
|
760
|
+
const { scalarKind: _authoredScalarKind, ...authoredField } = field;
|
|
761
|
+
const schemas = [];
|
|
762
|
+
for (const { objectType, property } of getPropertiesForResult(field.propertyId, shape, `${path}.fields[${index}]`, ctx)) {
|
|
763
|
+
if (property.query?.searchable !== true || property.query.sortable !== true) {
|
|
764
|
+
addIssue(ctx, `${path}.fields[${index}]`, "property_not_sortable", `Property '${property.id}' on '${objectType.id}' must set query.searchable: true and query.sortable: true before it can be sorted`);
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
const schema = resolveSchemaForProperty(property, objectType.id, ctx, `${path}.fields[${index}]`);
|
|
768
|
+
if (schema)
|
|
769
|
+
schemas.push(schema);
|
|
770
|
+
if (schema && !isSortableSchema(schema)) {
|
|
771
|
+
addIssue(ctx, `${path}.fields[${index}]`, "sort_field_not_orderable", `Sort field '${property.id}' on '${objectType.id}' is not orderable`);
|
|
772
|
+
}
|
|
749
773
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (op === "contains")
|
|
754
|
-
return resolveArrayItemScalarKind(schemas, ctx);
|
|
755
|
-
return resolveCommonQueryScalarKind(schemas, propertyId, path, ctx);
|
|
774
|
+
const scalarKind = resolveCommonQueryScalarKind(schemas, field.propertyId, `${path}.fields[${index}]`, ctx);
|
|
775
|
+
return { ...authoredField, ...scalarKind ? { scalarKind } : {} };
|
|
776
|
+
});
|
|
756
777
|
}
|
|
757
|
-
function
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
const items = resolveValueTypeRefs(schema.items, ctx.valueTypesById);
|
|
763
|
-
kinds.add(items ? queryScalarKindForSchema(items) : undefined);
|
|
778
|
+
function validateLimit(limit, path, ctx) {
|
|
779
|
+
if (!Number.isInteger(limit) || limit < 0) {
|
|
780
|
+
addIssue(ctx, path, "invalid_limit", "limit must be a non-negative integer");
|
|
781
|
+
} else if (limit > ctx.maxLimit) {
|
|
782
|
+
addIssue(ctx, path, "limit_too_large", `limit must be <= ${ctx.maxLimit}`);
|
|
764
783
|
}
|
|
765
|
-
return kinds.size === 1 ? [...kinds][0] : undefined;
|
|
766
|
-
}
|
|
767
|
-
function resolveValueTypeRefs(schema, valueTypesById, seen = new Set) {
|
|
768
|
-
if (typeof schema === "string" || schema.type !== "valueTypeRef")
|
|
769
|
-
return schema;
|
|
770
|
-
if (seen.has(schema.valueTypeId))
|
|
771
|
-
return;
|
|
772
|
-
seen.add(schema.valueTypeId);
|
|
773
|
-
const resolved = schema._resolved ?? valueTypesById.get(schema.valueTypeId)?.schema;
|
|
774
|
-
return resolved ? resolveValueTypeRefs(resolved, valueTypesById, seen) : undefined;
|
|
775
784
|
}
|
|
776
|
-
function
|
|
777
|
-
if (
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
782
|
-
|
|
785
|
+
function validatePage(pageSize, pageToken, path, ctx) {
|
|
786
|
+
if (!Number.isInteger(pageSize) || pageSize <= 0) {
|
|
787
|
+
addIssue(ctx, path, "invalid_page_size", "pageSize must be a positive integer");
|
|
788
|
+
} else if (pageSize > ctx.maxPageSize) {
|
|
789
|
+
addIssue(ctx, path, "page_size_too_large", `pageSize must be <= ${ctx.maxPageSize}`);
|
|
790
|
+
}
|
|
791
|
+
if (pageToken !== undefined && pageToken.length === 0) {
|
|
792
|
+
addIssue(ctx, path, "empty_page_token", "pageToken must not be empty when provided");
|
|
783
793
|
}
|
|
784
794
|
}
|
|
785
|
-
function
|
|
786
|
-
|
|
787
|
-
if (scalarKinds.length === 0 || scalarKinds.some((kind) => kind === undefined)) {
|
|
795
|
+
function validateProjection(properties, shape, admissionState, path, ctx) {
|
|
796
|
+
if (!properties)
|
|
788
797
|
return;
|
|
798
|
+
if (properties.length === 0) {
|
|
799
|
+
addIssue(ctx, path, "empty_projection", "project.properties must not be empty when provided");
|
|
789
800
|
}
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
try {
|
|
801
|
-
validatePropertyValue2(property, value, `${objectType.id}.${property.id}`, ctx.valueTypesById);
|
|
802
|
-
} catch (error) {
|
|
803
|
-
addIssue2(ctx, path, "invalid_predicate_value", error instanceof Error ? error.message : `Invalid value for property '${property.id}' on '${objectType.id}'`);
|
|
801
|
+
const seen = new Set;
|
|
802
|
+
properties.forEach((propertyId, index) => {
|
|
803
|
+
admitProperty(ctx, {
|
|
804
|
+
state: admissionState,
|
|
805
|
+
propertyId,
|
|
806
|
+
use: "project",
|
|
807
|
+
path: `${path}.properties[${index}]`
|
|
808
|
+
});
|
|
809
|
+
if (seen.has(propertyId)) {
|
|
810
|
+
addIssue(ctx, `${path}.properties[${index}]`, "duplicate_projection_property", `Duplicate projected property '${propertyId}'`);
|
|
804
811
|
}
|
|
805
|
-
|
|
812
|
+
seen.add(propertyId);
|
|
813
|
+
getPropertiesForResult(propertyId, shape, `${path}.properties[${index}]`, ctx);
|
|
814
|
+
});
|
|
806
815
|
}
|
|
807
|
-
function
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
continue;
|
|
814
|
-
if (typeof schema === "string") {
|
|
815
|
-
if (isContainsSchema(schema)) {
|
|
816
|
-
if (typeof value !== "string") {
|
|
817
|
-
addIssue2(ctx, path, "invalid_contains_value", `Predicate 'contains' value for '${objectType.id}.${property.id}' must be a string`);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
continue;
|
|
821
|
-
}
|
|
822
|
-
if (schema.type === "array") {
|
|
823
|
-
try {
|
|
824
|
-
validateSchemaValue2(schema.items, value, `${objectType.id}.${property.id}[]`, ctx.valueTypesById);
|
|
825
|
-
} catch (error) {
|
|
826
|
-
addIssue2(ctx, path, "invalid_contains_value", error instanceof Error ? error.message : `Invalid contains value for '${objectType.id}.${property.id}'`);
|
|
827
|
-
}
|
|
828
|
-
continue;
|
|
829
|
-
}
|
|
830
|
-
if (schema.type === "map" && typeof value !== "string") {
|
|
831
|
-
addIssue2(ctx, path, "invalid_contains_value", `Predicate 'contains' value for '${objectType.id}.${property.id}' must be a map key string`);
|
|
832
|
-
}
|
|
816
|
+
function getObjectTypesForResult(shape, ctx) {
|
|
817
|
+
const objectTypes = [];
|
|
818
|
+
for (const objectTypeId of shape.objectTypeIds) {
|
|
819
|
+
const objectType = ctx.ontology.getObjectTypeById(objectTypeId);
|
|
820
|
+
if (objectType)
|
|
821
|
+
objectTypes.push(objectType);
|
|
833
822
|
}
|
|
823
|
+
return objectTypes;
|
|
834
824
|
}
|
|
835
|
-
function
|
|
836
|
-
|
|
837
|
-
addIssue2(ctx, path, "empty_text_query", "Text query must not be empty");
|
|
838
|
-
}
|
|
839
|
-
const fieldsByObjectType = fields ? undefined : {};
|
|
825
|
+
function getPropertiesForResult(propertyId, shape, path, ctx, options = {}) {
|
|
826
|
+
const results = [];
|
|
840
827
|
for (const objectType of getObjectTypesForResult(shape, ctx)) {
|
|
841
|
-
const
|
|
842
|
-
if (!
|
|
843
|
-
|
|
844
|
-
continue;
|
|
845
|
-
}
|
|
846
|
-
if (fieldsByObjectType)
|
|
847
|
-
fieldsByObjectType[objectType.id] = uniqueStrings(fieldIds);
|
|
848
|
-
for (const fieldId of fieldIds) {
|
|
849
|
-
admitProperty(ctx, {
|
|
850
|
-
state: admissionState,
|
|
851
|
-
propertyId: fieldId,
|
|
852
|
-
objectTypeId: objectType.id,
|
|
853
|
-
use: "text",
|
|
854
|
-
path: `${path}.fields`
|
|
855
|
-
});
|
|
856
|
-
const property = getProperty(objectType, fieldId);
|
|
857
|
-
if (!property) {
|
|
858
|
-
addIssue2(ctx, path, "unknown_text_field", `Text query references unknown property '${fieldId}' on '${objectType.id}'`);
|
|
828
|
+
const property = getProperty(objectType, propertyId);
|
|
829
|
+
if (!property) {
|
|
830
|
+
if (options.reportMissing === false) {
|
|
859
831
|
continue;
|
|
860
832
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
const schema = resolveSchemaForProperty(property, objectType.id, ctx, path);
|
|
864
|
-
if (schema && !isTextSchema(schema)) {
|
|
865
|
-
addIssue2(ctx, path, "text_field_not_string_like", `Text query field '${fieldId}' on '${objectType.id}' must be string-like`);
|
|
866
|
-
}
|
|
833
|
+
addIssue(ctx, path, "unknown_property", `Property '${propertyId}' does not exist on object type '${objectType.id}'`);
|
|
834
|
+
continue;
|
|
867
835
|
}
|
|
836
|
+
results.push({ objectType, property });
|
|
868
837
|
}
|
|
869
|
-
return
|
|
838
|
+
return results;
|
|
870
839
|
}
|
|
871
|
-
function
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
let input = query.input;
|
|
878
|
-
while (input.kind === "filter")
|
|
879
|
-
input = input.input;
|
|
880
|
-
if (input.kind !== "start" || input.includeSubtypes)
|
|
881
|
-
addIssue2(ctx, path, "unsupported_vector_composition", "Vector profile input must be a type with optional filters.");
|
|
882
|
-
const objectType = types[0];
|
|
883
|
-
const profile = query.profile === undefined ? undefined : objectType.search?.vectors?.[query.profile];
|
|
884
|
-
if (!profile) {
|
|
885
|
-
addIssue2(ctx, path, "unknown_vector_profile", `Unknown vector profile: ${query.profile}`);
|
|
886
|
-
return query;
|
|
840
|
+
function getProperty(objectType, propertyId) {
|
|
841
|
+
return objectType.properties.find((property) => property.id === propertyId);
|
|
842
|
+
}
|
|
843
|
+
function assertStaticSearchProperty(objectTypeId, property, context, path, ctx) {
|
|
844
|
+
if (property.mode === "telemetry") {
|
|
845
|
+
addIssue(ctx, path, "telemetry_search_property", `${context} references telemetry property '${property.id}' on '${objectTypeId}'. Search can only reference static properties because telemetry latest values are not object-query indexed.`);
|
|
887
846
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
847
|
+
}
|
|
848
|
+
function assertQueryFlag(objectTypeId, property, flag, context, path, ctx) {
|
|
849
|
+
if (property.query?.searchable === true && property.query[flag] === true)
|
|
850
|
+
return;
|
|
851
|
+
addIssue(ctx, path, "query_field_not_enabled", `${context} field '${property.id}' on '${objectTypeId}' must set query.searchable: true and query.${flag}: true`);
|
|
852
|
+
}
|
|
853
|
+
function resolveSchemaForProperty(property, objectTypeId, ctx, path) {
|
|
891
854
|
try {
|
|
892
|
-
|
|
893
|
-
if (!values.trim() || values.length > 8000) {
|
|
894
|
-
throw new Error("Vector search text must be nonempty and at most 8000 characters.");
|
|
895
|
-
}
|
|
896
|
-
} else {
|
|
897
|
-
values = normalizeVector2(values, profile.model.definition.dimensions);
|
|
898
|
-
}
|
|
855
|
+
return resolveSchema(property.schema, ctx.valueTypesById);
|
|
899
856
|
} catch (error) {
|
|
900
|
-
|
|
857
|
+
addIssue(ctx, path, "unresolved_property_schema", error instanceof Error ? error.message : `Could not resolve schema for '${objectTypeId}.${property.id}'`);
|
|
858
|
+
return null;
|
|
901
859
|
}
|
|
902
|
-
return {
|
|
903
|
-
...query,
|
|
904
|
-
vector: values,
|
|
905
|
-
source: [...profile.source],
|
|
906
|
-
configuration: vectorConfiguration(profile)
|
|
907
|
-
};
|
|
908
860
|
}
|
|
909
|
-
function
|
|
910
|
-
if (
|
|
911
|
-
|
|
912
|
-
|
|
861
|
+
function resolveSchema(schema, valueTypesById, seenValueTypeIds = new Set) {
|
|
862
|
+
if (typeof schema === "string")
|
|
863
|
+
return schema;
|
|
864
|
+
if (schema.type !== "valueTypeRef")
|
|
865
|
+
return schema;
|
|
866
|
+
if (seenValueTypeIds.has(schema.valueTypeId)) {
|
|
867
|
+
throw new Error(`Circular valueTypeRef '${schema.valueTypeId}'`);
|
|
913
868
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
}
|
|
918
|
-
return validateOutgoingTraverse(linkId, shape, path, ctx);
|
|
869
|
+
const resolved = schema._resolved ?? valueTypesById.get(schema.valueTypeId)?.schema;
|
|
870
|
+
if (!resolved) {
|
|
871
|
+
throw new Error(`Unknown valueTypeRef '${schema.valueTypeId}'`);
|
|
919
872
|
}
|
|
920
|
-
|
|
873
|
+
seenValueTypeIds.add(schema.valueTypeId);
|
|
874
|
+
return resolveSchema(resolved, valueTypesById, seenValueTypeIds);
|
|
921
875
|
}
|
|
922
|
-
function
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
876
|
+
function isPrimaryExactPredicate(property, op) {
|
|
877
|
+
return property.primary === true && (op === "eq" || op === "in");
|
|
878
|
+
}
|
|
879
|
+
function keyForTypeIds(typeIds) {
|
|
880
|
+
return uniqueStrings(typeIds).sort().join("\x00");
|
|
881
|
+
}
|
|
882
|
+
function uniqueStrings(values) {
|
|
883
|
+
return [...new Set(values)];
|
|
884
|
+
}
|
|
885
|
+
function admitTransition(ctx, transition) {
|
|
886
|
+
if (transition.denial)
|
|
887
|
+
recordAdmissionDenial(ctx, transition.denial);
|
|
888
|
+
return transition.state;
|
|
889
|
+
}
|
|
890
|
+
function admitProperty(ctx, input) {
|
|
891
|
+
const denial = ctx.admission.property(input);
|
|
892
|
+
if (denial)
|
|
893
|
+
recordAdmissionDenial(ctx, denial);
|
|
894
|
+
}
|
|
895
|
+
function recordAdmissionDenial(ctx, denial) {
|
|
896
|
+
if (ctx.admissionDenials.length === 0)
|
|
897
|
+
ctx.admissionDenials.push(denial);
|
|
898
|
+
}
|
|
899
|
+
function addIssue(ctx, path, code, message) {
|
|
900
|
+
ctx.issues.push({ path, code, message });
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// src/objects/vectors/resolve-query.ts
|
|
904
|
+
var QUERY_EMBEDDING_TIMEOUT_MS = 30000;
|
|
905
|
+
async function resolveVectorSearchText(query, ontology, models, signal) {
|
|
906
|
+
if (query.kind === "limit" || query.kind === "project") {
|
|
907
|
+
const input = await resolveVectorSearchText(query.input, ontology, models, signal);
|
|
908
|
+
return { ...query, input };
|
|
942
909
|
}
|
|
943
|
-
|
|
910
|
+
if (query.kind !== "vector" || typeof query.vector !== "string")
|
|
911
|
+
return query;
|
|
912
|
+
const model = resolveProfileEmbeddingModel(query, ontology, models);
|
|
913
|
+
const vector = await embedSearchText(query.vector, model, signal);
|
|
914
|
+
return { ...query, vector };
|
|
944
915
|
}
|
|
945
|
-
function
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
candidateTypes = [sourceType];
|
|
916
|
+
function resolveProfileEmbeddingModel(query, ontology, models) {
|
|
917
|
+
const { objectTypeIds } = resolveObjectQueryResultShape2(query.input, {
|
|
918
|
+
ontology,
|
|
919
|
+
normalize: false
|
|
920
|
+
});
|
|
921
|
+
const objectTypeId = objectTypeIds.length === 1 ? objectTypeIds[0] : undefined;
|
|
922
|
+
if (!objectTypeId || !query.profile) {
|
|
923
|
+
throw new ObjectQueryExecutionError2("embedding_model_unavailable", "Vector search requires the embedding model registered for its profile.");
|
|
954
924
|
}
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if (!link)
|
|
959
|
-
continue;
|
|
960
|
-
if (link.targetObjectTypeId === "*") {
|
|
961
|
-
sourceTypeIds.push(sourceType.id);
|
|
962
|
-
continue;
|
|
963
|
-
}
|
|
964
|
-
const matches = shape.objectTypeIds.some((targetTypeId) => ctx.ontology.isValidLinkTarget(link.targetObjectTypeId, targetTypeId));
|
|
965
|
-
if (matches)
|
|
966
|
-
sourceTypeIds.push(sourceType.id);
|
|
925
|
+
const profile = ontology.resolveObjectType(objectTypeId).search?.vectors?.[query.profile];
|
|
926
|
+
if (!profile) {
|
|
927
|
+
throw new ObjectQueryExecutionError2("embedding_model_unavailable", "Vector search requires the embedding model registered for its profile.");
|
|
967
928
|
}
|
|
968
|
-
|
|
969
|
-
|
|
929
|
+
const model = models?.getByRef({
|
|
930
|
+
provider: profile.model.providerId,
|
|
931
|
+
modelId: profile.model.modelId
|
|
932
|
+
})?.model;
|
|
933
|
+
if (!model || !sameEmbeddingModel(model, profile.model)) {
|
|
934
|
+
throw new ObjectQueryExecutionError2("embedding_model_unavailable", "Vector search requires the embedding model registered for its profile.");
|
|
970
935
|
}
|
|
971
|
-
return
|
|
972
|
-
}
|
|
973
|
-
function validateExpansions(expansions, shape, admissionState, path, ctx) {
|
|
974
|
-
return expansions.map((expansion, index) => validateExpansion(expansion, shape, admissionState, `${path}[${index}]`, ctx));
|
|
936
|
+
return model;
|
|
975
937
|
}
|
|
976
|
-
function
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
938
|
+
async function embedSearchText(text, model, signal) {
|
|
939
|
+
const dimensions = model.definition.dimensions;
|
|
940
|
+
const timeout = AbortSignal.timeout(QUERY_EMBEDDING_TIMEOUT_MS);
|
|
941
|
+
const requestSignal = signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
942
|
+
requestSignal.throwIfAborted();
|
|
943
|
+
const result = await model.embed({ texts: [text], purpose: "query", signal: requestSignal });
|
|
944
|
+
requestSignal.throwIfAborted();
|
|
945
|
+
if (!Array.isArray(result?.vectors) || result.vectors.length !== 1) {
|
|
946
|
+
throw new ObjectQueryExecutionError2("invalid_embedding_response", "Embedding model must return exactly one vector for the search text.");
|
|
980
947
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
linkId: expansion.linkId,
|
|
986
|
-
direction: expansion.direction,
|
|
987
|
-
sourceObjectTypeId: expansion.sourceObjectTypeId,
|
|
988
|
-
use: "expand",
|
|
989
|
-
path
|
|
990
|
-
}));
|
|
991
|
-
for (const targetTypeId of targetShape.objectTypeIds) {
|
|
992
|
-
ctx.touchedObjectTypeIds.add(targetTypeId);
|
|
948
|
+
try {
|
|
949
|
+
return normalizeVector2(result.vectors[0], dimensions);
|
|
950
|
+
} catch {
|
|
951
|
+
throw new ObjectQueryExecutionError2("invalid_embedding_response", "Embedding model returned an invalid vector for the search profile.");
|
|
993
952
|
}
|
|
994
|
-
validateExpansionLimit(expansion.limit, path, ctx);
|
|
995
|
-
const orderBy = expansion.orderBy ? validateSortFields(expansion.orderBy, targetShape, targetAdmissionState, "expand.orderBy", `${path}.orderBy`, ctx) : undefined;
|
|
996
|
-
const expand = expansion.expand ? validateExpansions(expansion.expand, targetShape, targetAdmissionState, `${path}.expand`, ctx) : undefined;
|
|
997
|
-
return { ...expansion, orderBy, expand };
|
|
998
953
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
954
|
+
|
|
955
|
+
// src/objects/query/planner.ts
|
|
956
|
+
var DEFAULT_MAX_FALLBACK_ROWS = 1000;
|
|
957
|
+
var fallbackNodeKinds = new Set([
|
|
958
|
+
"start",
|
|
959
|
+
"refs",
|
|
960
|
+
"filter",
|
|
961
|
+
"sort",
|
|
962
|
+
"limit",
|
|
963
|
+
"page",
|
|
964
|
+
"project",
|
|
965
|
+
"expand"
|
|
966
|
+
]);
|
|
967
|
+
function planObjectQuery2(query, options) {
|
|
968
|
+
const operation = options.operation ?? "queryObjects";
|
|
969
|
+
const providerIssues = collectProviderIssues(query, options.capabilities, {
|
|
970
|
+
operation,
|
|
971
|
+
hasImplementation: hasProviderImplementation(operation, options)
|
|
972
|
+
});
|
|
973
|
+
if (providerIssues.length === 0) {
|
|
974
|
+
return {
|
|
975
|
+
mode: "pushdown",
|
|
976
|
+
query,
|
|
977
|
+
providerIssues,
|
|
978
|
+
fallbackIssues: [],
|
|
979
|
+
issues: []
|
|
980
|
+
};
|
|
1002
981
|
}
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
for (const targetId of declaredTargets) {
|
|
1016
|
-
if (!ctx.ontology.getObjectTypeById(targetId)) {
|
|
1017
|
-
addIssue2(ctx, path, "unknown_expand_target", `Expansion over '${objectType.id}.${linkId}' targets unknown object type '${targetId}'`);
|
|
1018
|
-
} else {
|
|
1019
|
-
targetTypeIds.push(targetId);
|
|
982
|
+
const fallbackIssues = collectFallbackIssues(query, options);
|
|
983
|
+
const issues = [...providerIssues, ...fallbackIssues];
|
|
984
|
+
if (fallbackIssues.length === 0) {
|
|
985
|
+
return {
|
|
986
|
+
mode: "fallback",
|
|
987
|
+
query,
|
|
988
|
+
providerIssues,
|
|
989
|
+
fallbackIssues,
|
|
990
|
+
issues: [],
|
|
991
|
+
fallback: {
|
|
992
|
+
maxRows: options.maxFallbackRows ?? DEFAULT_MAX_FALLBACK_ROWS,
|
|
993
|
+
requiresExplicitBound: options.requiresExplicitFallbackBound !== false
|
|
1020
994
|
}
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
if (!foundOnAnyType) {
|
|
1024
|
-
addIssue2(ctx, path, "unknown_expand_link", `Expansion references link '${linkId}' not present on result type ${shape.objectTypeIds.join(" | ")} (an expand abandoned by a later traverse?)`);
|
|
995
|
+
};
|
|
1025
996
|
}
|
|
1026
|
-
return {
|
|
997
|
+
return {
|
|
998
|
+
mode: "rejected",
|
|
999
|
+
query,
|
|
1000
|
+
providerIssues,
|
|
1001
|
+
fallbackIssues,
|
|
1002
|
+
issues
|
|
1003
|
+
};
|
|
1027
1004
|
}
|
|
1028
|
-
function
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
for (const sourceType of candidateTypes) {
|
|
1040
|
-
const link = sourceType.links.find((candidate) => candidate.id === linkId);
|
|
1041
|
-
if (!link)
|
|
1042
|
-
continue;
|
|
1043
|
-
if (link.targetObjectTypeId === "*") {
|
|
1044
|
-
sourceTypeIds.push(sourceType.id);
|
|
1045
|
-
continue;
|
|
1046
|
-
}
|
|
1047
|
-
const matches = shape.objectTypeIds.some((targetTypeId) => ctx.ontology.isValidLinkTarget(link.targetObjectTypeId, targetTypeId));
|
|
1048
|
-
if (matches)
|
|
1049
|
-
sourceTypeIds.push(sourceType.id);
|
|
1005
|
+
function collectProviderIssues(query, capabilities, options) {
|
|
1006
|
+
const issues = [];
|
|
1007
|
+
collectOperationProviderIssues(capabilities, options, issues);
|
|
1008
|
+
collectNodeProviderIssues(query, "$", capabilities, issues);
|
|
1009
|
+
return issues;
|
|
1010
|
+
}
|
|
1011
|
+
function collectOperationProviderIssues(capabilities, options, issues) {
|
|
1012
|
+
const enabled = options.operation === "queryObjects" ? capabilities.queryObjects === true : options.operation === "countObjects" ? capabilities.countObjects === true : options.operation === "existsObjects" ? capabilities.existsObjects === true : capabilities.facetObjects === true;
|
|
1013
|
+
if (!enabled) {
|
|
1014
|
+
const code = `${operationIssuePrefix(options.operation)}_not_enabled`;
|
|
1015
|
+
addIssue2(issues, "$", code, `Object storage capabilities do not enable ${options.operation} pushdown`);
|
|
1050
1016
|
}
|
|
1051
|
-
if (
|
|
1052
|
-
|
|
1017
|
+
if (options.hasImplementation)
|
|
1018
|
+
return;
|
|
1019
|
+
const code = `${operationIssuePrefix(options.operation)}_not_implemented`;
|
|
1020
|
+
addIssue2(issues, "$", code, `Object storage does not implement ${options.operation}`);
|
|
1021
|
+
}
|
|
1022
|
+
function hasProviderImplementation(operation, options) {
|
|
1023
|
+
switch (operation) {
|
|
1024
|
+
case "queryObjects":
|
|
1025
|
+
return options.hasQueryObjects === true;
|
|
1026
|
+
case "countObjects":
|
|
1027
|
+
return options.hasCountObjects === true;
|
|
1028
|
+
case "existsObjects":
|
|
1029
|
+
return options.hasExistsObjects === true;
|
|
1030
|
+
case "facetObjects":
|
|
1031
|
+
return options.hasFacetObjects === true;
|
|
1053
1032
|
}
|
|
1054
|
-
return { objectTypeIds: uniqueStrings(sourceTypeIds) };
|
|
1055
1033
|
}
|
|
1056
|
-
function
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1034
|
+
function operationIssuePrefix(operation) {
|
|
1035
|
+
switch (operation) {
|
|
1036
|
+
case "queryObjects":
|
|
1037
|
+
return "query_objects";
|
|
1038
|
+
case "countObjects":
|
|
1039
|
+
return "count_objects";
|
|
1040
|
+
case "existsObjects":
|
|
1041
|
+
return "exists_objects";
|
|
1042
|
+
case "facetObjects":
|
|
1043
|
+
return "facet_objects";
|
|
1061
1044
|
}
|
|
1062
1045
|
}
|
|
1063
|
-
function
|
|
1064
|
-
if (
|
|
1065
|
-
addIssue2(
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1046
|
+
function collectNodeProviderIssues(query, path, capabilities, issues) {
|
|
1047
|
+
if (capabilities.nodes?.[query.kind] !== true) {
|
|
1048
|
+
addIssue2(issues, path, "query_node_not_supported", `Provider does not support query node '${query.kind}'`);
|
|
1049
|
+
}
|
|
1050
|
+
switch (query.kind) {
|
|
1051
|
+
case "start":
|
|
1052
|
+
if (query.includeSubtypes === true && capabilities.features?.includeSubtypes !== true) {
|
|
1053
|
+
addIssue2(issues, path, "include_subtypes_not_supported", "Provider does not support start.includeSubtypes expansion");
|
|
1054
|
+
}
|
|
1055
|
+
return;
|
|
1056
|
+
case "refs":
|
|
1057
|
+
return;
|
|
1058
|
+
case "filter":
|
|
1059
|
+
collectPredicateProviderIssues(query.predicate, `${path}.predicate`, capabilities, issues);
|
|
1060
|
+
collectNodeProviderIssues(query.input, `${path}.input`, capabilities, issues);
|
|
1061
|
+
return;
|
|
1062
|
+
case "text":
|
|
1063
|
+
case "vector":
|
|
1064
|
+
case "project":
|
|
1065
|
+
collectNodeProviderIssues(query.input, `${path}.input`, capabilities, issues);
|
|
1066
|
+
return;
|
|
1067
|
+
case "limit":
|
|
1068
|
+
collectProviderMaxLimitIssues(query.limit, path, capabilities, issues);
|
|
1069
|
+
collectNodeProviderIssues(query.input, `${path}.input`, capabilities, issues);
|
|
1070
|
+
return;
|
|
1071
|
+
case "page":
|
|
1072
|
+
collectProviderMaxPageSizeIssues(query.pageSize, path, capabilities, issues);
|
|
1073
|
+
collectNodeProviderIssues(query.input, `${path}.input`, capabilities, issues);
|
|
1074
|
+
return;
|
|
1075
|
+
case "traverse":
|
|
1076
|
+
if (capabilities.traversalDirections?.[query.direction] !== true) {
|
|
1077
|
+
addIssue2(issues, path, "traversal_direction_not_supported", `Provider does not support ${query.direction} traversal`);
|
|
1078
|
+
}
|
|
1079
|
+
collectNodeProviderIssues(query.input, `${path}.input`, capabilities, issues);
|
|
1080
|
+
return;
|
|
1081
|
+
case "set":
|
|
1082
|
+
if (capabilities.setOps?.[query.op] !== true) {
|
|
1083
|
+
addIssue2(issues, path, "set_operation_not_supported", `Provider does not support set operation '${query.op}'`);
|
|
1084
|
+
}
|
|
1085
|
+
query.inputs.forEach((input, index) => {
|
|
1086
|
+
collectNodeProviderIssues(input, `${path}.inputs[${index}]`, capabilities, issues);
|
|
1087
|
+
});
|
|
1088
|
+
return;
|
|
1089
|
+
case "sort":
|
|
1090
|
+
query.fields.forEach((field, index) => {
|
|
1091
|
+
collectSortProviderIssues(field, `${path}.fields[${index}]`, capabilities, issues);
|
|
1092
|
+
});
|
|
1093
|
+
collectNodeProviderIssues(query.input, `${path}.input`, capabilities, issues);
|
|
1094
|
+
return;
|
|
1095
|
+
case "expand":
|
|
1096
|
+
query.expansions.forEach((expansion, index) => {
|
|
1097
|
+
collectExpansionProviderIssues(expansion, `${path}.expansions[${index}]`, capabilities, issues);
|
|
1098
|
+
});
|
|
1099
|
+
collectNodeProviderIssues(query.input, `${path}.input`, capabilities, issues);
|
|
1100
|
+
return;
|
|
1071
1101
|
}
|
|
1072
|
-
const results = inputs.map((input, index) => validateQueryNode(input, `${path}.inputs[${index}]`, ctx));
|
|
1073
|
-
const first = results[0]?.result ?? { objectTypeIds: [] };
|
|
1074
|
-
const expected = keyForTypeIds(first.objectTypeIds);
|
|
1075
|
-
results.forEach((result, index) => {
|
|
1076
|
-
if (keyForTypeIds(result.result.objectTypeIds) !== expected) {
|
|
1077
|
-
addIssue2(ctx, `${path}.inputs[${index}]`, "incompatible_set_input", `Set operation '${op}' inputs must produce the same object type set`);
|
|
1078
|
-
}
|
|
1079
|
-
});
|
|
1080
|
-
return {
|
|
1081
|
-
result: first,
|
|
1082
|
-
query: { kind: "set", op, inputs: results.map((result) => result.query) },
|
|
1083
|
-
admissionState: ctx.admission.set({
|
|
1084
|
-
op,
|
|
1085
|
-
states: results.map((result) => result.admissionState),
|
|
1086
|
-
path
|
|
1087
|
-
})
|
|
1088
|
-
};
|
|
1089
1102
|
}
|
|
1090
|
-
function
|
|
1091
|
-
if (
|
|
1092
|
-
addIssue2(
|
|
1093
|
-
return [];
|
|
1103
|
+
function collectExpansionProviderIssues(expansion, path, capabilities, issues) {
|
|
1104
|
+
if (expansion.cardinality === undefined) {
|
|
1105
|
+
addIssue2(issues, path, "expand_cardinality_unresolved", `Provider cannot push down expansion '${expansion.linkId}' without a resolved cardinality`);
|
|
1094
1106
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
}
|
|
1100
|
-
const key = field.kind === "property" ? `property:${field.propertyId}` : "relevance";
|
|
1101
|
-
if (seen.has(key)) {
|
|
1102
|
-
addIssue2(ctx, `${path}.fields[${index}]`, "duplicate_sort_field", `Duplicate sort field '${key}'`);
|
|
1103
|
-
}
|
|
1104
|
-
seen.add(key);
|
|
1105
|
-
if (field.kind === "relevance")
|
|
1106
|
-
return field;
|
|
1107
|
-
admitProperty(ctx, {
|
|
1108
|
-
state: admissionState,
|
|
1109
|
-
propertyId: field.propertyId,
|
|
1110
|
-
use,
|
|
1111
|
-
path: `${path}.fields[${index}]`
|
|
1112
|
-
});
|
|
1113
|
-
const { scalarKind: _authoredScalarKind, ...authoredField } = field;
|
|
1114
|
-
const schemas = [];
|
|
1115
|
-
for (const { objectType, property } of getPropertiesForResult(field.propertyId, shape, `${path}.fields[${index}]`, ctx)) {
|
|
1116
|
-
if (property.query?.searchable !== true || property.query.sortable !== true) {
|
|
1117
|
-
addIssue2(ctx, `${path}.fields[${index}]`, "property_not_sortable", `Property '${property.id}' on '${objectType.id}' must set query.searchable: true and query.sortable: true before it can be sorted`);
|
|
1118
|
-
continue;
|
|
1119
|
-
}
|
|
1120
|
-
const schema = resolveSchemaForProperty(property, objectType.id, ctx, `${path}.fields[${index}]`);
|
|
1121
|
-
if (schema)
|
|
1122
|
-
schemas.push(schema);
|
|
1123
|
-
if (schema && !isSortableSchema(schema)) {
|
|
1124
|
-
addIssue2(ctx, `${path}.fields[${index}]`, "sort_field_not_orderable", `Sort field '${property.id}' on '${objectType.id}' is not orderable`);
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
const scalarKind = resolveCommonQueryScalarKind(schemas, field.propertyId, `${path}.fields[${index}]`, ctx);
|
|
1128
|
-
return { ...authoredField, ...scalarKind ? { scalarKind } : {} };
|
|
1107
|
+
expansion.orderBy?.forEach((field, index) => {
|
|
1108
|
+
collectSortProviderIssues(field, `${path}.orderBy[${index}]`, capabilities, issues);
|
|
1109
|
+
});
|
|
1110
|
+
expansion.expand?.forEach((nested, index) => {
|
|
1111
|
+
collectExpansionProviderIssues(nested, `${path}.expand[${index}]`, capabilities, issues);
|
|
1129
1112
|
});
|
|
1130
1113
|
}
|
|
1131
|
-
function
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
addIssue2(ctx, path, "limit_too_large", `limit must be <= ${ctx.maxLimit}`);
|
|
1114
|
+
function collectPredicateProviderIssues(predicate, path, capabilities, issues) {
|
|
1115
|
+
const scalarOperation = predicateScalarOperation(predicate);
|
|
1116
|
+
if ("scalarKind" in predicate && predicate.scalarKind !== undefined && scalarOperation !== undefined && capabilities.scalarOperations?.[predicate.scalarKind]?.[scalarOperation] !== true) {
|
|
1117
|
+
addIssue2(issues, path, "scalar_operation_not_supported", `Provider does not support ${scalarOperation} for '${predicate.scalarKind}' values`);
|
|
1136
1118
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
if (!Number.isInteger(pageSize) || pageSize <= 0) {
|
|
1140
|
-
addIssue2(ctx, path, "invalid_page_size", "pageSize must be a positive integer");
|
|
1141
|
-
} else if (pageSize > ctx.maxPageSize) {
|
|
1142
|
-
addIssue2(ctx, path, "page_size_too_large", `pageSize must be <= ${ctx.maxPageSize}`);
|
|
1119
|
+
if (capabilities.predicateOps?.[predicate.op] !== true) {
|
|
1120
|
+
addIssue2(issues, path, "predicate_operation_not_supported", `Provider does not support predicate operation '${predicate.op}'`);
|
|
1143
1121
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1122
|
+
switch (predicate.op) {
|
|
1123
|
+
case "and":
|
|
1124
|
+
case "or":
|
|
1125
|
+
predicate.items.forEach((item, index) => {
|
|
1126
|
+
collectPredicateProviderIssues(item, `${path}.items[${index}]`, capabilities, issues);
|
|
1127
|
+
});
|
|
1128
|
+
return;
|
|
1129
|
+
case "not":
|
|
1130
|
+
collectPredicateProviderIssues(predicate.item, `${path}.item`, capabilities, issues);
|
|
1131
|
+
return;
|
|
1132
|
+
default:
|
|
1133
|
+
return;
|
|
1146
1134
|
}
|
|
1147
1135
|
}
|
|
1148
|
-
function
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1136
|
+
function predicateScalarOperation(predicate) {
|
|
1137
|
+
switch (predicate.op) {
|
|
1138
|
+
case "eq":
|
|
1139
|
+
case "neq":
|
|
1140
|
+
case "in":
|
|
1141
|
+
case "contains":
|
|
1142
|
+
return "equality";
|
|
1143
|
+
case "lt":
|
|
1144
|
+
case "lte":
|
|
1145
|
+
case "gt":
|
|
1146
|
+
case "gte":
|
|
1147
|
+
return "ordering";
|
|
1148
|
+
default:
|
|
1149
|
+
return;
|
|
1153
1150
|
}
|
|
1154
|
-
const seen = new Set;
|
|
1155
|
-
properties.forEach((propertyId, index) => {
|
|
1156
|
-
admitProperty(ctx, {
|
|
1157
|
-
state: admissionState,
|
|
1158
|
-
propertyId,
|
|
1159
|
-
use: "project",
|
|
1160
|
-
path: `${path}.properties[${index}]`
|
|
1161
|
-
});
|
|
1162
|
-
if (seen.has(propertyId)) {
|
|
1163
|
-
addIssue2(ctx, `${path}.properties[${index}]`, "duplicate_projection_property", `Duplicate projected property '${propertyId}'`);
|
|
1164
|
-
}
|
|
1165
|
-
seen.add(propertyId);
|
|
1166
|
-
getPropertiesForResult(propertyId, shape, `${path}.properties[${index}]`, ctx);
|
|
1167
|
-
});
|
|
1168
1151
|
}
|
|
1169
|
-
function
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
const objectType = ctx.ontology.getObjectTypeById(objectTypeId);
|
|
1173
|
-
if (objectType)
|
|
1174
|
-
objectTypes.push(objectType);
|
|
1152
|
+
function collectSortProviderIssues(field, path, capabilities, issues) {
|
|
1153
|
+
if (field.kind === "property" && field.scalarKind !== undefined && capabilities.scalarOperations?.[field.scalarKind]?.ordering !== true) {
|
|
1154
|
+
addIssue2(issues, path, "scalar_operation_not_supported", `Provider does not support ordering for '${field.scalarKind}' values`);
|
|
1175
1155
|
}
|
|
1176
|
-
|
|
1156
|
+
if (capabilities.sortKinds?.[field.kind] === true)
|
|
1157
|
+
return;
|
|
1158
|
+
addIssue2(issues, path, "sort_kind_not_supported", `Provider does not support sort kind '${field.kind}'`);
|
|
1177
1159
|
}
|
|
1178
|
-
function
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
}
|
|
1186
|
-
addIssue2(ctx, path, "unknown_property", `Property '${propertyId}' does not exist on object type '${objectType.id}'`);
|
|
1187
|
-
continue;
|
|
1188
|
-
}
|
|
1189
|
-
results.push({ objectType, property });
|
|
1160
|
+
function collectProviderMaxLimitIssues(limit, path, capabilities, issues) {
|
|
1161
|
+
const maxLimit = capabilities.limits?.maxLimit;
|
|
1162
|
+
if (maxLimit === undefined)
|
|
1163
|
+
return;
|
|
1164
|
+
if (!Number.isInteger(maxLimit) || maxLimit < 0) {
|
|
1165
|
+
addIssue2(issues, path, "invalid_provider_limit_capability", "Provider limit capability maxLimit must be a non-negative integer");
|
|
1166
|
+
return;
|
|
1190
1167
|
}
|
|
1191
|
-
|
|
1192
|
-
}
|
|
1193
|
-
function getProperty(objectType, propertyId) {
|
|
1194
|
-
return objectType.properties.find((property) => property.id === propertyId);
|
|
1195
|
-
}
|
|
1196
|
-
function assertStaticSearchProperty(objectTypeId, property, context, path, ctx) {
|
|
1197
|
-
if (property.mode === "telemetry") {
|
|
1198
|
-
addIssue2(ctx, path, "telemetry_search_property", `${context} references telemetry property '${property.id}' on '${objectTypeId}'. Search can only reference static properties because telemetry latest values are not object-query indexed.`);
|
|
1168
|
+
if (limit > maxLimit) {
|
|
1169
|
+
addIssue2(issues, path, "provider_limit_too_large", `Provider supports limit up to ${maxLimit}`);
|
|
1199
1170
|
}
|
|
1200
1171
|
}
|
|
1201
|
-
function
|
|
1202
|
-
|
|
1172
|
+
function collectProviderMaxPageSizeIssues(pageSize, path, capabilities, issues) {
|
|
1173
|
+
const maxPageSize = capabilities.limits?.maxPageSize;
|
|
1174
|
+
if (maxPageSize === undefined)
|
|
1175
|
+
return;
|
|
1176
|
+
if (!Number.isInteger(maxPageSize) || maxPageSize <= 0) {
|
|
1177
|
+
addIssue2(issues, path, "invalid_provider_page_size_capability", "Provider limit capability maxPageSize must be a positive integer");
|
|
1203
1178
|
return;
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
try {
|
|
1208
|
-
return resolveSchema(property.schema, ctx.valueTypesById);
|
|
1209
|
-
} catch (error) {
|
|
1210
|
-
addIssue2(ctx, path, "unresolved_property_schema", error instanceof Error ? error.message : `Could not resolve schema for '${objectTypeId}.${property.id}'`);
|
|
1211
|
-
return null;
|
|
1179
|
+
}
|
|
1180
|
+
if (pageSize > maxPageSize) {
|
|
1181
|
+
addIssue2(issues, path, "provider_page_size_too_large", `Provider supports pageSize up to ${maxPageSize}`);
|
|
1212
1182
|
}
|
|
1213
1183
|
}
|
|
1214
|
-
function
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
return
|
|
1219
|
-
if (seenValueTypeIds.has(schema.valueTypeId)) {
|
|
1220
|
-
throw new Error(`Circular valueTypeRef '${schema.valueTypeId}'`);
|
|
1184
|
+
function collectFallbackIssues(query, options) {
|
|
1185
|
+
const issues = [];
|
|
1186
|
+
if (options.allowFallback === false) {
|
|
1187
|
+
addIssue2(issues, "$", "fallback_disabled", "Object query fallback is disabled");
|
|
1188
|
+
return issues;
|
|
1221
1189
|
}
|
|
1222
|
-
const
|
|
1223
|
-
if (!
|
|
1224
|
-
|
|
1190
|
+
const maxFallbackRows = options.maxFallbackRows ?? DEFAULT_MAX_FALLBACK_ROWS;
|
|
1191
|
+
if (!Number.isInteger(maxFallbackRows) || maxFallbackRows <= 0) {
|
|
1192
|
+
addIssue2(issues, "$", "invalid_fallback_bound", "maxFallbackRows must be a positive integer");
|
|
1225
1193
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
return
|
|
1231
|
-
}
|
|
1232
|
-
function keyForTypeIds(typeIds) {
|
|
1233
|
-
return uniqueStrings(typeIds).sort().join("\x00");
|
|
1234
|
-
}
|
|
1235
|
-
function uniqueStrings(values) {
|
|
1236
|
-
return [...new Set(values)];
|
|
1237
|
-
}
|
|
1238
|
-
function admitTransition(ctx, transition) {
|
|
1239
|
-
if (transition.denial)
|
|
1240
|
-
recordAdmissionDenial(ctx, transition.denial);
|
|
1241
|
-
return transition.state;
|
|
1194
|
+
if (options.requiresExplicitFallbackBound !== false && !hasExplicitResultBound(query)) {
|
|
1195
|
+
addIssue2(issues, "$", "fallback_requires_bound", "Fallback requires an explicit limit or page node");
|
|
1196
|
+
}
|
|
1197
|
+
collectFallbackNodeIssues(query, "$", issues);
|
|
1198
|
+
return issues;
|
|
1242
1199
|
}
|
|
1243
|
-
function
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1200
|
+
function collectFallbackNodeIssues(query, path, issues) {
|
|
1201
|
+
if (!fallbackNodeKinds.has(query.kind)) {
|
|
1202
|
+
addIssue2(issues, path, "fallback_node_not_supported", `Fallback does not support query node '${query.kind}'`);
|
|
1203
|
+
}
|
|
1204
|
+
switch (query.kind) {
|
|
1205
|
+
case "start":
|
|
1206
|
+
case "refs":
|
|
1207
|
+
return;
|
|
1208
|
+
case "filter":
|
|
1209
|
+
case "limit":
|
|
1210
|
+
case "page":
|
|
1211
|
+
case "project":
|
|
1212
|
+
case "expand":
|
|
1213
|
+
collectFallbackNodeIssues(query.input, `${path}.input`, issues);
|
|
1214
|
+
return;
|
|
1215
|
+
case "sort":
|
|
1216
|
+
query.fields.forEach((field, index) => {
|
|
1217
|
+
if (field.kind === "relevance") {
|
|
1218
|
+
addIssue2(issues, `${path}.fields[${index}]`, "fallback_sort_kind_not_supported", "Fallback does not support relevance sorting");
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
collectFallbackNodeIssues(query.input, `${path}.input`, issues);
|
|
1222
|
+
return;
|
|
1223
|
+
case "text":
|
|
1224
|
+
case "vector":
|
|
1225
|
+
case "traverse":
|
|
1226
|
+
collectFallbackNodeIssues(query.input, `${path}.input`, issues);
|
|
1227
|
+
return;
|
|
1228
|
+
case "set":
|
|
1229
|
+
query.inputs.forEach((input, index) => {
|
|
1230
|
+
collectFallbackNodeIssues(input, `${path}.inputs[${index}]`, issues);
|
|
1231
|
+
});
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1247
1234
|
}
|
|
1248
|
-
function
|
|
1249
|
-
|
|
1250
|
-
|
|
1235
|
+
function hasExplicitResultBound(query) {
|
|
1236
|
+
switch (query.kind) {
|
|
1237
|
+
case "limit":
|
|
1238
|
+
case "page":
|
|
1239
|
+
case "refs":
|
|
1240
|
+
return true;
|
|
1241
|
+
case "start":
|
|
1242
|
+
return false;
|
|
1243
|
+
case "set":
|
|
1244
|
+
return query.inputs.some(hasExplicitResultBound);
|
|
1245
|
+
default:
|
|
1246
|
+
return hasExplicitResultBound(query.input);
|
|
1247
|
+
}
|
|
1251
1248
|
}
|
|
1252
|
-
function addIssue2(
|
|
1253
|
-
|
|
1249
|
+
function addIssue2(issues, path, code, message) {
|
|
1250
|
+
issues.push({ path, code, message });
|
|
1254
1251
|
}
|
|
1255
1252
|
|
|
1256
1253
|
// src/objects/query/executor.ts
|
|
@@ -2460,6 +2457,6 @@ async function hydrateLinkQueryObjects(projectId, selected, links, storage) {
|
|
|
2460
2457
|
return row ? [row] : [];
|
|
2461
2458
|
});
|
|
2462
2459
|
}
|
|
2463
|
-
export {
|
|
2460
|
+
export { validateObjectQuery2, validateObjectQueryWithAdmission, collectObjectQueryValidationIssues2, resolveObjectQueryResultShape2, planObjectQuery2, executeObjectQuery2, countObjects2, existsObjects2, facetObjects2, validateObjectFacetRequests, explainObjectQuery2, preflightObjectQueryLinks, executeObjectQueryLinks2 };
|
|
2464
2461
|
|
|
2465
|
-
//# debugId=
|
|
2462
|
+
//# debugId=96E245CAF140894464756E2164756E21
|