@milaboratories/pl-model-common 1.31.2 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/drivers/index.d.ts +4 -4
- package/dist/drivers/pframe/index.d.ts +4 -4
- package/dist/drivers/pframe/query/index.d.ts +2 -2
- package/dist/drivers/pframe/query/query_common.d.ts +39 -1
- package/dist/drivers/pframe/query/query_common.d.ts.map +1 -1
- package/dist/drivers/pframe/query/query_data.d.ts +18 -3
- package/dist/drivers/pframe/query/query_data.d.ts.map +1 -1
- package/dist/drivers/pframe/query/query_spec.d.ts +14 -3
- package/dist/drivers/pframe/query/query_spec.d.ts.map +1 -1
- package/dist/drivers/pframe/query/utils.cjs +27 -0
- package/dist/drivers/pframe/query/utils.cjs.map +1 -1
- package/dist/drivers/pframe/query/utils.d.ts.map +1 -1
- package/dist/drivers/pframe/query/utils.js +27 -0
- package/dist/drivers/pframe/query/utils.js.map +1 -1
- package/dist/drivers/pframe/spec/anchored.cjs.map +1 -1
- package/dist/drivers/pframe/spec/anchored.d.ts +3 -3
- package/dist/drivers/pframe/spec/anchored.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/anchored.js.map +1 -1
- package/dist/drivers/pframe/spec/index.d.ts +1 -1
- package/dist/drivers/pframe/spec/spec.cjs +8 -3
- package/dist/drivers/pframe/spec/spec.cjs.map +1 -1
- package/dist/drivers/pframe/spec/spec.d.ts +15 -2
- package/dist/drivers/pframe/spec/spec.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/spec.js +8 -3
- package/dist/drivers/pframe/spec/spec.js.map +1 -1
- package/dist/drivers/pframe/spec_driver.d.ts +37 -2
- package/dist/drivers/pframe/spec_driver.d.ts.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/services/service_types.cjs.map +1 -1
- package/dist/services/service_types.d.ts +7 -7
- package/dist/services/service_types.d.ts.map +1 -1
- package/dist/services/service_types.js.map +1 -1
- package/package.json +6 -6
- package/src/drivers/pframe/query/query_common.ts +39 -0
- package/src/drivers/pframe/query/query_data.ts +20 -0
- package/src/drivers/pframe/query/query_spec.ts +17 -0
- package/src/drivers/pframe/query/utils.test.ts +37 -0
- package/src/drivers/pframe/query/utils.ts +25 -0
- package/src/drivers/pframe/spec/anchored.ts +8 -3
- package/src/drivers/pframe/spec/spec.ts +19 -6
- package/src/drivers/pframe/spec_driver.ts +42 -1
- package/src/services/service_types.ts +1 -6
|
@@ -74,6 +74,20 @@ describe("traverseQuerySpec", () => {
|
|
|
74
74
|
});
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
it("transforms columns inside linkerJoin", () => {
|
|
78
|
+
const q: Q = {
|
|
79
|
+
type: "linkerJoin",
|
|
80
|
+
linker: { column: "l" },
|
|
81
|
+
secondary: [entry(col("a")), entry(col("b"))],
|
|
82
|
+
};
|
|
83
|
+
const result = traverseQuerySpec(q, { column: (c) => c.toUpperCase() });
|
|
84
|
+
expect(result).toEqual({
|
|
85
|
+
type: "linkerJoin",
|
|
86
|
+
linker: { column: "L" },
|
|
87
|
+
secondary: [entry({ type: "column", column: "A" }), entry({ type: "column", column: "B" })],
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
77
91
|
it("transforms columns inside filter", () => {
|
|
78
92
|
const q: Q = {
|
|
79
93
|
type: "filter",
|
|
@@ -195,6 +209,15 @@ describe("collectSpecQueryColumns", () => {
|
|
|
195
209
|
} as unknown as Q;
|
|
196
210
|
expect(collectSpecQueryColumns(q)).toEqual([]);
|
|
197
211
|
});
|
|
212
|
+
|
|
213
|
+
it("collects linker and secondary columns from linkerJoin", () => {
|
|
214
|
+
const q: Q = {
|
|
215
|
+
type: "linkerJoin",
|
|
216
|
+
linker: { column: "l" },
|
|
217
|
+
secondary: [entry(col("a")), entry(col("b"))],
|
|
218
|
+
};
|
|
219
|
+
expect(collectSpecQueryColumns(q)).toEqual(["l", "a", "b"]);
|
|
220
|
+
});
|
|
198
221
|
});
|
|
199
222
|
|
|
200
223
|
describe("sortSpecQuery", () => {
|
|
@@ -221,6 +244,20 @@ describe("sortSpecQuery", () => {
|
|
|
221
244
|
});
|
|
222
245
|
});
|
|
223
246
|
|
|
247
|
+
it("sorts linkerJoin secondary entries (linker column unchanged)", () => {
|
|
248
|
+
const q: SpecQuery = {
|
|
249
|
+
type: "linkerJoin",
|
|
250
|
+
linker: { column: pid("l") },
|
|
251
|
+
secondary: [pentry(pcol("c")), pentry(pcol("a")), pentry(pcol("b"))],
|
|
252
|
+
};
|
|
253
|
+
const result = sortSpecQuery(q);
|
|
254
|
+
expect(result).toEqual({
|
|
255
|
+
type: "linkerJoin",
|
|
256
|
+
linker: { column: "l" },
|
|
257
|
+
secondary: [pentry(pcol("a")), pentry(pcol("b")), pentry(pcol("c"))],
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
224
261
|
it("sorts sparseToDenseColumn axesIndices", () => {
|
|
225
262
|
const q: SpecQuery = {
|
|
226
263
|
type: "sparseToDenseColumn",
|
|
@@ -78,6 +78,13 @@ export function traverseQuerySpec<C1, C2>(
|
|
|
78
78
|
secondary: query.secondary.map(traverseEntry),
|
|
79
79
|
};
|
|
80
80
|
break;
|
|
81
|
+
case "linkerJoin":
|
|
82
|
+
result = {
|
|
83
|
+
...query,
|
|
84
|
+
linker: { ...query.linker, column: visitor.column(query.linker.column) },
|
|
85
|
+
secondary: query.secondary.map(traverseEntry),
|
|
86
|
+
};
|
|
87
|
+
break;
|
|
81
88
|
case "filter":
|
|
82
89
|
case "sort":
|
|
83
90
|
case "sliceAxes":
|
|
@@ -126,6 +133,10 @@ export function sortSpecQuery(query: SpecQuery): SpecQuery {
|
|
|
126
133
|
const sorted = [...node.secondary].sort(cmpQueryJoinEntrySpec);
|
|
127
134
|
return { ...node, secondary: sorted };
|
|
128
135
|
}
|
|
136
|
+
case "linkerJoin": {
|
|
137
|
+
const sorted = [...node.secondary].sort(cmpQueryJoinEntrySpec);
|
|
138
|
+
return { ...node, secondary: sorted };
|
|
139
|
+
}
|
|
129
140
|
case "sliceAxes":
|
|
130
141
|
return {
|
|
131
142
|
...node,
|
|
@@ -198,6 +209,20 @@ function cmpQuerySpec(lhs: SpecQuery, rhs: SpecQuery): number {
|
|
|
198
209
|
}
|
|
199
210
|
return 0;
|
|
200
211
|
}
|
|
212
|
+
case "linkerJoin": {
|
|
213
|
+
const rhsLinker = rhs as typeof lhs;
|
|
214
|
+
if (lhs.linker.column !== rhsLinker.linker.column) {
|
|
215
|
+
return lhs.linker.column < rhsLinker.linker.column ? -1 : 1;
|
|
216
|
+
}
|
|
217
|
+
if (lhs.secondary.length !== rhsLinker.secondary.length) {
|
|
218
|
+
return lhs.secondary.length - rhsLinker.secondary.length;
|
|
219
|
+
}
|
|
220
|
+
for (let i = 0; i < lhs.secondary.length; i++) {
|
|
221
|
+
const cmp = cmpQueryJoinEntrySpec(lhs.secondary[i], rhsLinker.secondary[i]);
|
|
222
|
+
if (cmp !== 0) return cmp;
|
|
223
|
+
}
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
201
226
|
case "sliceAxes":
|
|
202
227
|
return cmpQuerySpec(lhs.input, (rhs as typeof lhs).input);
|
|
203
228
|
case "sort":
|
|
@@ -50,7 +50,12 @@ export class AnchoredIdDeriver {
|
|
|
50
50
|
* Creates a new anchor context from a set of anchor column specifications
|
|
51
51
|
* @param anchors Record of anchor column specifications indexed by anchor ID
|
|
52
52
|
*/
|
|
53
|
-
constructor(
|
|
53
|
+
constructor(
|
|
54
|
+
public readonly anchors: Record<
|
|
55
|
+
string,
|
|
56
|
+
Pick<PColumnSpec, "axesSpec" | "domain" | "contextDomain">
|
|
57
|
+
>,
|
|
58
|
+
) {
|
|
54
59
|
const anchorEntries = Object.entries(anchors);
|
|
55
60
|
anchorEntries.sort((a, b) => a[0].localeCompare(b[0]));
|
|
56
61
|
for (const [anchorId, spec] of anchorEntries) {
|
|
@@ -241,7 +246,7 @@ export type ResolveAnchorsOptions = {
|
|
|
241
246
|
* @returns A non-anchored column matcher with all references resolved to actual values
|
|
242
247
|
*/
|
|
243
248
|
export function resolveAnchors(
|
|
244
|
-
anchors: Record<string, PColumnSpec
|
|
249
|
+
anchors: Record<string, Pick<PColumnSpec, "axesSpec" | "domain" | "contextDomain">>,
|
|
245
250
|
matcher: AnchoredPColumnSelector,
|
|
246
251
|
options?: ResolveAnchorsOptions,
|
|
247
252
|
): PColumnSelector {
|
|
@@ -321,7 +326,7 @@ export function resolveAnchors(
|
|
|
321
326
|
* Resolves an anchored axis reference to a concrete AxisId
|
|
322
327
|
*/
|
|
323
328
|
function resolveAxisReference(
|
|
324
|
-
anchors: Record<string, PColumnSpec
|
|
329
|
+
anchors: Record<string, Pick<PColumnSpec, "axesSpec">>,
|
|
325
330
|
axisRef: AAxisSelector,
|
|
326
331
|
): LegacyAxisSelector {
|
|
327
332
|
if (!isAnchorAxisRef(axisRef)) return axisRef;
|
|
@@ -145,6 +145,7 @@ export const Annotation = {
|
|
|
145
145
|
IsScore: "pl7.app/isScore",
|
|
146
146
|
IsSubset: "pl7.app/isSubset",
|
|
147
147
|
Label: "pl7.app/label",
|
|
148
|
+
LinkLabel: "pl7.app/linkLabel",
|
|
148
149
|
Max: "pl7.app/max",
|
|
149
150
|
Min: "pl7.app/min",
|
|
150
151
|
MultipliesBy: "pl7.app/multipliesBy",
|
|
@@ -204,10 +205,24 @@ export type Annotation = Metadata &
|
|
|
204
205
|
[Annotation.Table.FontFamily]: string;
|
|
205
206
|
[Annotation.Table.OrderPriority]: StringifiedJson<number>;
|
|
206
207
|
[Annotation.Table.Visibility]: "hidden" | "optional" | (string & {});
|
|
207
|
-
[Annotation.Trace]: StringifiedJson<
|
|
208
|
+
[Annotation.Trace]: StringifiedJson<Trace>;
|
|
208
209
|
[Annotation.VDJ.IsAssemblingFeature]: StringifiedJson<boolean>;
|
|
209
210
|
}>;
|
|
210
211
|
|
|
212
|
+
/// One step in a column's derivation lineage. Only `type` and `label` are part
|
|
213
|
+
/// of the typed public surface; writers may add fields (e.g. `id`, `importance`)
|
|
214
|
+
/// passed through opaquely as `unknown`, accessed via explicit narrowing.
|
|
215
|
+
const TraceEntrySchema = z
|
|
216
|
+
.object({
|
|
217
|
+
type: z.string(),
|
|
218
|
+
label: z.string(),
|
|
219
|
+
})
|
|
220
|
+
.catchall(z.unknown());
|
|
221
|
+
|
|
222
|
+
export type TraceEntry = z.infer<typeof TraceEntrySchema>;
|
|
223
|
+
|
|
224
|
+
export type Trace = TraceEntry[];
|
|
225
|
+
|
|
211
226
|
// export const AxisSpec = z.object({
|
|
212
227
|
// type: z.nativeEnum(ValueType),
|
|
213
228
|
// name: z.string(),
|
|
@@ -256,7 +271,7 @@ export const AnnotationJson: AnnotationJson = {
|
|
|
256
271
|
[Annotation.Sequence.Annotation.Mapping]: z.record(z.string(), z.string()),
|
|
257
272
|
[Annotation.Sequence.IsAnnotation]: z.boolean(),
|
|
258
273
|
[Annotation.Table.OrderPriority]: z.number(),
|
|
259
|
-
[Annotation.Trace]: z.
|
|
274
|
+
[Annotation.Trace]: z.array(TraceEntrySchema),
|
|
260
275
|
[Annotation.VDJ.IsAssemblingFeature]: z.boolean(),
|
|
261
276
|
};
|
|
262
277
|
|
|
@@ -527,11 +542,9 @@ export function getNormalizedAxesList(axes: AxisSpec[]): AxisSpecNormalized[] {
|
|
|
527
542
|
|
|
528
543
|
/** Create list of regular axisSpec from normalized (parents are indexes, inside of current axes list) */
|
|
529
544
|
export function getDenormalizedAxesList(axesSpec: AxisSpecNormalized[]): AxisSpec[] {
|
|
530
|
-
const idsList = axesSpec.map(
|
|
545
|
+
const idsList = axesSpec.map(canonicalizeAxisId);
|
|
531
546
|
return axesSpec.map((axisSpec) => {
|
|
532
|
-
const parentsIds = axisSpec.parentAxesSpec.map(
|
|
533
|
-
canonicalizeJson(getAxisId(axisSpec)),
|
|
534
|
-
);
|
|
547
|
+
const parentsIds = axisSpec.parentAxesSpec.map(canonicalizeAxisId);
|
|
535
548
|
const parentIdxs = parentsIds.map((id) => idsList.indexOf(id));
|
|
536
549
|
const { parentAxesSpec: _, ...copiedRest } = axisSpec;
|
|
537
550
|
if (parentIdxs.length) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Branded } from "@milaboratories/helpers";
|
|
2
2
|
import type { PoolEntry } from "../../pool_entry";
|
|
3
|
+
import type { PObjectId } from "../../pool";
|
|
3
4
|
import type {
|
|
4
5
|
AxisSpec,
|
|
5
6
|
AxesSpec,
|
|
@@ -111,8 +112,48 @@ export interface DiscoverColumnsLinkerStep {
|
|
|
111
112
|
qualifications: AxisQualification[];
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Filter step: intersects the current subquery with a filter column on shared
|
|
117
|
+
* axes. Filter columns carry `pl7.app/isSubset: "true"` with axes ⊆ dataset
|
|
118
|
+
* axes, so the inner-join narrows the key space to rows where the filter is
|
|
119
|
+
* present.
|
|
120
|
+
*/
|
|
121
|
+
export interface DiscoverColumnsFilterStep {
|
|
122
|
+
type: "filter";
|
|
123
|
+
/** The filter column applied in this step */
|
|
124
|
+
filter: PColumnIdAndSpec;
|
|
125
|
+
}
|
|
126
|
+
|
|
114
127
|
/** A step traversed during path-based column discovery. Discriminated by `type`. */
|
|
115
|
-
export type DiscoverColumnsStepInfo = DiscoverColumnsLinkerStep;
|
|
128
|
+
export type DiscoverColumnsStepInfo = DiscoverColumnsLinkerStep | DiscoverColumnsFilterStep;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Input to {@link PFrameWasmV4.buildQuery}: a terminal column plus an ordered
|
|
132
|
+
* path of wrapping steps (linker hops, filter joins). Produces a
|
|
133
|
+
* {@link SpecQueryJoinEntry} ready to be plugged into an
|
|
134
|
+
* `innerJoin`/`fullJoin`/`outerJoin` entry list.
|
|
135
|
+
*
|
|
136
|
+
* Path ordering: `path[0]` is outermost (first applied), `path[N-1]` is
|
|
137
|
+
* closest to `column`. Omit or pass `[]` for a direct column with no
|
|
138
|
+
* wrapping.
|
|
139
|
+
*
|
|
140
|
+
* Columns are referenced by id — specs are resolved later at
|
|
141
|
+
* {@link PFrameWasmV4.evaluateQuery} against the registered specs of the
|
|
142
|
+
* PFrame, so the caller cannot disagree with the frame about spec content.
|
|
143
|
+
*
|
|
144
|
+
* Qualifications annotate the resulting outermost entry; they do not
|
|
145
|
+
* propagate into the inner query.
|
|
146
|
+
*/
|
|
147
|
+
export type BuildQueryInput = {
|
|
148
|
+
/** Shape version marker — bumped only on breaking structural changes. */
|
|
149
|
+
readonly version: "v1";
|
|
150
|
+
/** Terminal column id — the column actually returning data. */
|
|
151
|
+
readonly column: PObjectId;
|
|
152
|
+
/** Ordered path from source integration to `column`. Outermost first. */
|
|
153
|
+
readonly path?: DiscoverColumnsStepInfo[];
|
|
154
|
+
/** Axis qualifications attached to the resulting join entry. */
|
|
155
|
+
readonly qualifications?: AxisQualification[];
|
|
156
|
+
};
|
|
116
157
|
|
|
117
158
|
/** Qualifications info for a discover columns response mapping variant */
|
|
118
159
|
export interface DiscoverColumnsResponseQualifications {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UnionToIntersection } from "@milaboratories/helpers";
|
|
1
2
|
import type { Branded } from "../branding";
|
|
2
3
|
import { ServiceAlreadyRegisteredError, ServiceInvalidIdError } from "../errors";
|
|
3
4
|
import type { Services } from "./service_declarations";
|
|
@@ -101,12 +102,6 @@ export type ServiceRequireFlags = {
|
|
|
101
102
|
[K in keyof SMap as `requires${K & string}`]?: boolean;
|
|
102
103
|
};
|
|
103
104
|
|
|
104
|
-
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (
|
|
105
|
-
k: infer I,
|
|
106
|
-
) => void
|
|
107
|
-
? I
|
|
108
|
-
: never;
|
|
109
|
-
|
|
110
105
|
export type RequireServices<T extends ServiceName> = UnionToIntersection<
|
|
111
106
|
T extends Branded<infer N extends string, infer S extends ServiceTypesLike>
|
|
112
107
|
? Record<N, InferServiceModel<S>>
|