@hockeymask/core 0.1.0 → 0.1.1
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/index.d.mts +12 -11
- package/dist/index.mjs +24 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -206,7 +206,8 @@ export declare class SourceUnavailable extends ConnectorError {
|
|
|
206
206
|
//#region src/mapping/result.d.ts
|
|
207
207
|
/**
|
|
208
208
|
* Failure as a value. Steps, nodes and the compiler return `Result` instead of throwing, so
|
|
209
|
-
* the executor can apply each node's failure policy (arch.md,
|
|
209
|
+
* the executor can apply each node's failure policy (arch.md,
|
|
210
|
+
* "Failure policy and partial results").
|
|
210
211
|
*/
|
|
211
212
|
export type Result<T, E> = {
|
|
212
213
|
readonly ok: true;
|
|
@@ -231,7 +232,7 @@ export interface StepError {
|
|
|
231
232
|
readonly code: ErrorCode;
|
|
232
233
|
readonly detail: string;
|
|
233
234
|
}
|
|
234
|
-
/** A runtime failure located in the graph
|
|
235
|
+
/** A runtime failure located in the graph. */
|
|
235
236
|
export interface MappingError extends StepError {
|
|
236
237
|
readonly node: string;
|
|
237
238
|
readonly op: string;
|
|
@@ -913,7 +914,7 @@ interface Origin {
|
|
|
913
914
|
interface NodeOutput {
|
|
914
915
|
readonly type: RecordType;
|
|
915
916
|
readonly origins: Readonly<Record<string, Origin>>;
|
|
916
|
-
/** Can drop records on a failed field
|
|
917
|
+
/** Can drop records on a failed field. */
|
|
917
918
|
readonly canSkip?: boolean;
|
|
918
919
|
}
|
|
919
920
|
/** An input slot: the param path that holds an upstream node id. */
|
|
@@ -977,7 +978,7 @@ interface SinkWrite {
|
|
|
977
978
|
* The rows are the complete set of the target for the company (and source system, where
|
|
978
979
|
* the identity has one): the host calls `synchronize_all(rows, scope)`, deleting rows not
|
|
979
980
|
* written. Writes of one run go in one transaction, inserts parent-first and deletes
|
|
980
|
-
* child-first (
|
|
981
|
+
* child-first (join-table rows before the entities they reference). A sink that does not write
|
|
981
982
|
* (held, stopped, failed) deletes nothing.
|
|
982
983
|
*/
|
|
983
984
|
readonly mode?: 'replace';
|
|
@@ -1022,7 +1023,7 @@ interface RunOptions {
|
|
|
1022
1023
|
interface CompiledNode {
|
|
1023
1024
|
/** Null for a node without an output relation (`sink`); `metrics` types what it reports. */
|
|
1024
1025
|
readonly output: NodeOutput | null;
|
|
1025
|
-
/** Target entities a sink writes (one sink per entity, structural rule
|
|
1026
|
+
/** Target entities a sink writes (one sink per entity, structural rule 4). */
|
|
1026
1027
|
readonly writes?: readonly string[];
|
|
1027
1028
|
readonly run: (inputs: Inputs, ctx: RunContext, options?: RunOptions) => Result<NodeRun, NodeFailure>;
|
|
1028
1029
|
}
|
|
@@ -1040,7 +1041,7 @@ interface NodeDef<P = unknown> {
|
|
|
1040
1041
|
readonly defaults: () => Record<string, unknown>;
|
|
1041
1042
|
/** Parses the node object without `id`, `op` and `label`. */
|
|
1042
1043
|
readonly params: z.ZodType<P>;
|
|
1043
|
-
/** Accepts `tolerate_skips` (
|
|
1044
|
+
/** Accepts `tolerate_skips` (arch.md, "Failure policy and partial results"). */
|
|
1044
1045
|
readonly toleratesSkips?: (params: P) => boolean;
|
|
1045
1046
|
readonly compile: (params: P, env: NodeEnv) => Result<CompiledNode, readonly NodeIssue[]>;
|
|
1046
1047
|
}
|
|
@@ -1361,7 +1362,7 @@ export declare function topologicalOrder(nodes: readonly NodeJson[]): string[];
|
|
|
1361
1362
|
export declare function sourceRecordType(schema: JsonSchema): Result<RecordType, string>;
|
|
1362
1363
|
/** A starting `expectedSchema` for a source table, from what its connector describes. */
|
|
1363
1364
|
export declare function tableJsonSchema(table: Table, system?: string): JsonSchema;
|
|
1364
|
-
/** The pinned schema of an internal entity read as a source
|
|
1365
|
+
/** The pinned schema of an internal entity read as a source. */
|
|
1365
1366
|
export declare function entityJsonSchema(entity: TargetEntity): JsonSchema;
|
|
1366
1367
|
/** Does a runtime value inhabit this type? */
|
|
1367
1368
|
export declare function conforms(value: Val, type: MType): boolean;
|
|
@@ -1389,12 +1390,12 @@ interface OpDef<P = unknown> {
|
|
|
1389
1390
|
readonly nulls: 'propagate' | 'consume';
|
|
1390
1391
|
/**
|
|
1391
1392
|
* What the op reads: 'value' (default) needs a piped value; 'none' starts a pipeline and
|
|
1392
|
-
* takes no piped input
|
|
1393
|
+
* takes no piped input; 'any' accepts either (`when`).
|
|
1393
1394
|
*/
|
|
1394
1395
|
readonly input?: 'value' | 'none' | 'any';
|
|
1395
1396
|
/** Must be the last step (`omit_if_null`). */
|
|
1396
1397
|
readonly terminal?: boolean;
|
|
1397
|
-
/** Whether this op can return an error at runtime, given its params
|
|
1398
|
+
/** Whether this op can return an error at runtime, given its params. */
|
|
1398
1399
|
readonly fallible: (params: P, env: RecordEnv, input: MType) => boolean;
|
|
1399
1400
|
/** Output type for an input type; an error message when the input is not accepted. */
|
|
1400
1401
|
readonly signature: (input: MType, params: P, env: RecordEnv) => Result<MType, string>;
|
|
@@ -1437,8 +1438,8 @@ export type StepJson = {
|
|
|
1437
1438
|
export declare function expandStep(raw: StepJson): Result<StepJson, string>;
|
|
1438
1439
|
/**
|
|
1439
1440
|
* The form a version is stored and compiled in: sugar expanded, `lookup_entity` rewritten
|
|
1440
|
-
* into graph nodes, and every internal source's schema pinned from the target model
|
|
1441
|
-
*
|
|
1441
|
+
* into graph nodes, and every internal source's schema pinned from the target model.
|
|
1442
|
+
* Idempotent.
|
|
1442
1443
|
*/
|
|
1443
1444
|
export declare function normalizeVersion(version: MappingVersion, env: Pick<GraphEnv, 'targets'>): {
|
|
1444
1445
|
version: MappingVersion;
|
package/dist/index.mjs
CHANGED
|
@@ -64,7 +64,7 @@ function stepError(code, detail) {
|
|
|
64
64
|
* The runtime value of the `date` type is an `Instant`: a frozen point in time in epoch
|
|
65
65
|
* milliseconds. Calendar math ("+10y", "start of day") happens in an IANA time zone, using
|
|
66
66
|
* `Intl.DateTimeFormat` to move between instants and wall-clock time. "now" only ever comes
|
|
67
|
-
* from the run context (arch.md
|
|
67
|
+
* from the run context (arch.md, "Time"), never from `Date.now()`.
|
|
68
68
|
*/
|
|
69
69
|
var Instant = class Instant {
|
|
70
70
|
epochMs;
|
|
@@ -609,7 +609,7 @@ function typeName(text) {
|
|
|
609
609
|
* A record is a plain frozen-by-convention object; an absent key reads as null (that is how
|
|
610
610
|
* `omit_if_null` and `skip_field` leave a field out of a write). Nothing here depends on
|
|
611
611
|
* insertion order: hashing and sorting go through `canonicalKey` and `compareVals`, so two
|
|
612
|
-
* runs over the same snapshot produce identical output (arch.md
|
|
612
|
+
* runs over the same snapshot produce identical output (arch.md, "Execution model").
|
|
613
613
|
*/
|
|
614
614
|
/** Returned by a step to leave its field out of the record entirely (`omit_if_null`). */
|
|
615
615
|
const OMIT = Symbol("omit");
|
|
@@ -698,8 +698,8 @@ function sortRecords(rows, fields) {
|
|
|
698
698
|
//#endregion
|
|
699
699
|
//#region src/mapping/predicate.ts
|
|
700
700
|
/**
|
|
701
|
-
* The one predicate vocabulary
|
|
702
|
-
*
|
|
701
|
+
* The one predicate vocabulary, shared by `when`, `filter_list`, the `filter` node, join
|
|
702
|
+
* `where`, aggregate `where`, and `rows` expectations.
|
|
703
703
|
*
|
|
704
704
|
* Shape: `{ path?, <predicate>: operand }` with exactly one predicate key, or a composition
|
|
705
705
|
* `{ all: { of: [...] } }`, `{ any: { of: [...] } }`, `{ not: cond }`. Without `path` the
|
|
@@ -707,7 +707,7 @@ function sortRecords(rows, fields) {
|
|
|
707
707
|
* `{ param: name }`.
|
|
708
708
|
*
|
|
709
709
|
* Null never compares: ordering and equality with a null side are false, `neq` and `not_in`
|
|
710
|
-
* are their negations and so hold for null (arch.md
|
|
710
|
+
* are their negations and so hold for null (arch.md, "Null semantics").
|
|
711
711
|
*/
|
|
712
712
|
const COMPARISONS = [
|
|
713
713
|
"eq",
|
|
@@ -1173,7 +1173,7 @@ function compileExpectation(node, exp, output, renv) {
|
|
|
1173
1173
|
//#endregion
|
|
1174
1174
|
//#region src/mapping/nodes/def.ts
|
|
1175
1175
|
/**
|
|
1176
|
-
* Collection node definitions
|
|
1176
|
+
* Collection node definitions.
|
|
1177
1177
|
*
|
|
1178
1178
|
* A node definition compiles itself: from its params and the types of its inputs it returns
|
|
1179
1179
|
* its output type and a closure, in one step, so type checking and compilation stay the same
|
|
@@ -1208,7 +1208,7 @@ function fail(message, extra = {}) {
|
|
|
1208
1208
|
* `aggregate`: one record per group of `group_by` (one record overall for `group_by: []`,
|
|
1209
1209
|
* even on empty input). Every aggregation takes an optional `where`, making it a filtered
|
|
1210
1210
|
* aggregate. Results never depend on input order: `sum` adds in sorted order, `collect_set`
|
|
1211
|
-
* is sorted, and `collect_list`, `first`, `last` require `order_by
|
|
1211
|
+
* is sorted, and `collect_list`, `first`, `last` require `order_by`.
|
|
1212
1212
|
*/
|
|
1213
1213
|
const orderBy = z.array(z.union([z.string().min(1), z.strictObject({
|
|
1214
1214
|
path: z.string().min(1),
|
|
@@ -1444,7 +1444,7 @@ const aggregateNode = defineNode({
|
|
|
1444
1444
|
//#endregion
|
|
1445
1445
|
//#region src/mapping/ops/def.ts
|
|
1446
1446
|
/**
|
|
1447
|
-
* Record op definitions
|
|
1447
|
+
* Record op definitions.
|
|
1448
1448
|
*
|
|
1449
1449
|
* An op is a params schema, a type function and a curried implementation. `signature` is
|
|
1450
1450
|
* the validator's half; `run` binds params once per compiled version and returns the
|
|
@@ -1599,7 +1599,7 @@ function versionJsonSchema() {
|
|
|
1599
1599
|
* - `unique`: `keys` unique.
|
|
1600
1600
|
* - `rows`: every row satisfies `cond`.
|
|
1601
1601
|
* - `acyclic`: the edges `edge.from -> edge.to` of `in` contain no cycle. With `edge.key` the
|
|
1602
|
-
* edges belong to groups (
|
|
1602
|
+
* edges belong to groups (each product's own supply graph) and each group is checked on its
|
|
1603
1603
|
* own; with `per`, every distinct `per.key` of `per.in` is checked against all edges. Either
|
|
1604
1604
|
* way a group's rows of `remove.in` are taken out first. Cycle detection is iterative, so
|
|
1605
1605
|
* it is engine code rather than a composition of DAG nodes.
|
|
@@ -2025,7 +2025,7 @@ function tableJsonSchema(table, system) {
|
|
|
2025
2025
|
description: c.description
|
|
2026
2026
|
})));
|
|
2027
2027
|
}
|
|
2028
|
-
/** The pinned schema of an internal entity read as a source
|
|
2028
|
+
/** The pinned schema of an internal entity read as a source. */
|
|
2029
2029
|
function entityJsonSchema(entity) {
|
|
2030
2030
|
return objectSchema(`internal.${entity.name}`, entity.fields.map((f) => ({
|
|
2031
2031
|
name: f.name,
|
|
@@ -2216,7 +2216,7 @@ const filterNode = defineNode({
|
|
|
2216
2216
|
* with the same name on both sides merges, and any other collision is a save error. Null keys
|
|
2217
2217
|
* never match.
|
|
2218
2218
|
*
|
|
2219
|
-
* `cardinality` is declared and enforced while the hash table is built
|
|
2219
|
+
* `cardinality` is declared and enforced while the hash table is built:
|
|
2220
2220
|
* `n:1` right keys unique, `1:n` left keys unique, `1:1` both, `n:m` unrestricted but needs a
|
|
2221
2221
|
* `count` expectation on this node.
|
|
2222
2222
|
*/
|
|
@@ -2940,7 +2940,7 @@ const requireOp = defineOp({
|
|
|
2940
2940
|
});
|
|
2941
2941
|
/**
|
|
2942
2942
|
* Terminal. A null leaves the field out of the record (and so out of the write) instead of
|
|
2943
|
-
* setting it to null
|
|
2943
|
+
* setting it to null. Readers downstream see the field as nullable.
|
|
2944
2944
|
*/
|
|
2945
2945
|
const omitIfNull = defineOp({
|
|
2946
2946
|
name: "omit_if_null",
|
|
@@ -3347,7 +3347,7 @@ const slug = defineOp({
|
|
|
3347
3347
|
//#endregion
|
|
3348
3348
|
//#region src/mapping/ops/when.ts
|
|
3349
3349
|
/**
|
|
3350
|
-
* `when`: the one general conditional
|
|
3350
|
+
* `when`: the one general conditional. First matching case wins;
|
|
3351
3351
|
* `then` and `else` are a literal, `null`, or `{ "pass": true }` to forward the input. `else`
|
|
3352
3352
|
* defaults to pass-through. As a pipeline's first step there is no input: every case must
|
|
3353
3353
|
* test a `path`, and `else` is required.
|
|
@@ -3458,7 +3458,7 @@ const opRegistry = Object.fromEntries(all$1.map((op) => [op.name, op]));
|
|
|
3458
3458
|
//#endregion
|
|
3459
3459
|
//#region src/mapping/sugar.ts
|
|
3460
3460
|
/**
|
|
3461
|
-
* Sugar, kept for readability and expanded on save
|
|
3461
|
+
* Sugar, kept for readability and expanded on save.
|
|
3462
3462
|
*
|
|
3463
3463
|
* - `{ op: "if_<predicate>", <operand>, then, else? }` expands to a one-case `when`, e.g.
|
|
3464
3464
|
* `{ op: "if_lte", value: 0, then: null }` to
|
|
@@ -3672,8 +3672,8 @@ function rewriteLookups(node, env, sources, issues) {
|
|
|
3672
3672
|
}
|
|
3673
3673
|
/**
|
|
3674
3674
|
* The form a version is stored and compiled in: sugar expanded, `lookup_entity` rewritten
|
|
3675
|
-
* into graph nodes, and every internal source's schema pinned from the target model
|
|
3676
|
-
*
|
|
3675
|
+
* into graph nodes, and every internal source's schema pinned from the target model.
|
|
3676
|
+
* Idempotent.
|
|
3677
3677
|
*/
|
|
3678
3678
|
function normalizeVersion(version, env) {
|
|
3679
3679
|
const issues = [];
|
|
@@ -4026,14 +4026,14 @@ function metric(node, name, rows) {
|
|
|
4026
4026
|
* fails at save, blamed on the `map` field that produced it.
|
|
4027
4027
|
*
|
|
4028
4028
|
* A reference field listed in `refs` is written as a natural key of the entity it references
|
|
4029
|
-
* (`product_id` as a SKU, a
|
|
4029
|
+
* (`product_id` as a SKU, a join-table row as its two keys) and typed by those fields, so a
|
|
4030
4030
|
* key of the wrong shape fails at save; the host resolves it to an id when it writes. Key
|
|
4031
4031
|
* fields must be non-null, except a nullable identity field of an entity whose identity
|
|
4032
|
-
* matches NULLs as equal (
|
|
4032
|
+
* matches NULLs as equal (a transfer with no origin, `from_warehouse_id = NULL`).
|
|
4033
4033
|
*
|
|
4034
4034
|
* `mode: 'replace'` makes the rows the complete set of the target for the company: the host
|
|
4035
4035
|
* reconciles with `synchronize_all`, deleting rows this run no longer produces. That is what a
|
|
4036
|
-
* presence-only entity needs (
|
|
4036
|
+
* presence-only entity needs (a join-table row *is* the link). Because a short source read
|
|
4037
4037
|
* would then delete real rows, `in` must carry a `count` expectation with a lower bound
|
|
4038
4038
|
* (`min` or `shiftRatio.min`) whose action is `stop` or `skip_downstream`.
|
|
4039
4039
|
*
|
|
@@ -4092,7 +4092,7 @@ const sinkNode = defineNode({
|
|
|
4092
4092
|
op: "sink",
|
|
4093
4093
|
label: "Write to target",
|
|
4094
4094
|
category: "target",
|
|
4095
|
-
description: "Writes rows into a
|
|
4095
|
+
description: "Writes rows into a target entity, updating records with the same key.",
|
|
4096
4096
|
slots: [{
|
|
4097
4097
|
id: "in",
|
|
4098
4098
|
label: "In",
|
|
@@ -4488,7 +4488,7 @@ function topologicalOrder(nodes) {
|
|
|
4488
4488
|
//#endregion
|
|
4489
4489
|
//#region src/mapping/compile.ts
|
|
4490
4490
|
/**
|
|
4491
|
-
* The graph compiler (arch.md, "
|
|
4491
|
+
* The graph compiler (arch.md, "Type checking"): walks nodes in topological order, threading
|
|
4492
4492
|
* record types along edges, and compiles each node into a closure in the same pass. The
|
|
4493
4493
|
* result is one plan per version; a run never touches JSON.
|
|
4494
4494
|
*
|
|
@@ -4690,7 +4690,7 @@ function dedupe(issues) {
|
|
|
4690
4690
|
return true;
|
|
4691
4691
|
});
|
|
4692
4692
|
}
|
|
4693
|
-
/** One sink per target entity (structural rule
|
|
4693
|
+
/** One sink per target entity (structural rule 4): resolve overlaps with a union. */
|
|
4694
4694
|
function sinkIssues(compiled) {
|
|
4695
4695
|
const writers = /* @__PURE__ */ new Map();
|
|
4696
4696
|
for (const [id, c] of compiled) for (const entity of c.node.writes ?? []) writers.set(entity, [...writers.get(entity) ?? [], id]);
|
|
@@ -4701,8 +4701,8 @@ function sinkIssues(compiled) {
|
|
|
4701
4701
|
}));
|
|
4702
4702
|
}
|
|
4703
4703
|
/**
|
|
4704
|
-
*
|
|
4705
|
-
*
|
|
4704
|
+
* A map that can drop records upstream of a join or aggregate can flip its result, so the
|
|
4705
|
+
* first join or aggregate on every path from it must declare `tolerate_skips`.
|
|
4706
4706
|
*/
|
|
4707
4707
|
function skipIssues(version, compiled) {
|
|
4708
4708
|
const next = consumers(version.nodes);
|