Scope and narrow

Hello, need to have nested objects but with only 1 key in each, i tried using narrow:
export const SQLBoolOperatorSchema = type({
_eq: "string|number|boolean",
})
.or({ _neq: "string|number|boolean" })
.or({ _gt: "string|number" });

const $boolField = scope({
ops: SQLBoolOperatorSchema,
record: {
"[string]": "value",
},
value: "ops | record",
field: () =>
$boolField
.type("record")
.narrow((x, ctx) => {
console.log(x);
return (
Object.keys(x).length === 1 ||
ctx.mustBe("accepting only one field for column comparison")
);
})
});

export const { field: SQLBoolFieldSchema } = $boolField.export();

// should error as access have multiple field
const foo = SQLBoolFieldSchema({
access: { id: { _eq: "$id" }, foo: { _eq: "12" } },
} as const);
if (foo instanceof ArkErrors) {
throw new Error(foo.summary);
}
export const SQLBoolOperatorSchema = type({
_eq: "string|number|boolean",
})
.or({ _neq: "string|number|boolean" })
.or({ _gt: "string|number" });

const $boolField = scope({
ops: SQLBoolOperatorSchema,
record: {
"[string]": "value",
},
value: "ops | record",
field: () =>
$boolField
.type("record")
.narrow((x, ctx) => {
console.log(x);
return (
Object.keys(x).length === 1 ||
ctx.mustBe("accepting only one field for column comparison")
);
})
});

export const { field: SQLBoolFieldSchema } = $boolField.export();

// should error as access have multiple field
const foo = SQLBoolFieldSchema({
access: { id: { _eq: "$id" }, foo: { _eq: "12" } },
} as const);
if (foo instanceof ArkErrors) {
throw new Error(foo.summary);
}
it doesnt work after 1st level, as i think i also need to narrow the "value" field.
7 Replies
papy0977
papy0977OP2w ago
so i tried:
const $boolField = scope({
ops: SQLBoolOperatorSchema,
record: {
"[string]": "n_value",
},
value: "ops | record",
n_value: () =>
$boolField.type("value").narrow((x, ctx) => {
console.log(x);
return (
Object.keys(x).length === 1 ||
ctx.mustBe("accepting only one field for column comparison")
);
}),
field: () =>
$boolField
.type("record")
.narrow((x, ctx) => {
console.log(x);
return (
Object.keys(x).length === 1 ||
ctx.mustBe("accepting only one field for column comparison")
);
})
});
const $boolField = scope({
ops: SQLBoolOperatorSchema,
record: {
"[string]": "n_value",
},
value: "ops | record",
n_value: () =>
$boolField.type("value").narrow((x, ctx) => {
console.log(x);
return (
Object.keys(x).length === 1 ||
ctx.mustBe("accepting only one field for column comparison")
);
}),
field: () =>
$boolField
.type("record")
.narrow((x, ctx) => {
console.log(x);
return (
Object.keys(x).length === 1 ||
ctx.mustBe("accepting only one field for column comparison")
);
})
});
the record is now rejected at runtime but: 1. i loose all the typing ('dvalue' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions) 2. get a warning Type of property '"[string]"' circularly references itself in mapped type 'validateObjectLiteral<{ readonly "[string]": "dvalue"; }...
ssalbdivad
ssalbdivad2w ago
Unfortunately we're somewhat beholden to what TS decides it can resolve in these cases. Did you try using a tuple expression? That's usually the most reliable way to do something like this in a scope
papy0977
papy0977OP2w ago
Hi, what do you mean by using a tuple expression ?
ssalbdivad
ssalbdivad2w ago
For most expressions like narrow you can write it without chaining in a tuple. You can see that syntax with the tuple tab in docs: https://arktype.io/docs/expressions#narrow
papy0977
papy0977OP2w ago
thanks will look if i can do something ok just try a simple tuple for the field, but got an error: 89 | return true 90 | } 91 | if (!ctx.seen.value2) { 92 | ctx.seen.value2 = [] 93 | } 94 | return this.value2Apply(data, ctx) ^ TypeError: this.value2Apply is not a function. (In 'this.value2Apply(data, ctx)', 'this.value2Apply' is undefined) at alias9Apply (node_modules/@ark/util/out/functions.js:94:28) at structure169Apply (node_modules/@ark/util/out/functions.js:59:29) at intersection601Apply (node_modules/@ark/util/out/functions.js:22:27) at <anonymous> (node_modules\@ark\schema\out\node.js:22:18)
const $tuBoolField = scope({
ops: SQLBoolOperatorSchema,
value: "ops | field",
field: [
{ "[string]": "value" },
":",
(x, ctx) => {
console.log("narrow field", x);
return (
Object.keys(x).length === 1 ||
ctx.mustBe("accepting only one field for column comparison")
);
},
],
});
export const { field: tuBoolFieldSchema } = $tuBoolField.export();

const tuFoo = tuBoolFieldSchema({
access: {
// id: { _eq: "$id", _neq: "foo" },
foo: { bar: { _eq: "12" }, hello: { eq: "hello" } },
},
// id: { _eq: "$id" },
} as const);
if (tuFoo instanceof ArkErrors) {
throw new Error(tuFoo.summary);
}
const $tuBoolField = scope({
ops: SQLBoolOperatorSchema,
value: "ops | field",
field: [
{ "[string]": "value" },
":",
(x, ctx) => {
console.log("narrow field", x);
return (
Object.keys(x).length === 1 ||
ctx.mustBe("accepting only one field for column comparison")
);
},
],
});
export const { field: tuBoolFieldSchema } = $tuBoolField.export();

const tuFoo = tuBoolFieldSchema({
access: {
// id: { _eq: "$id", _neq: "foo" },
foo: { bar: { _eq: "12" }, hello: { eq: "hello" } },
},
// id: { _eq: "$id" },
} as const);
if (tuFoo instanceof ArkErrors) {
throw new Error(tuFoo.summary);
}
ssalbdivad
ssalbdivad2w ago
Oh I see I didn't realize initially this was cyclic. The value2Apply issue looks like a bug but it's related to some other issues with cyclic unions: https://github.com/arktypeio/arktype/issues/1188 This workaround seems to work for your case as well: https://github.com/arktypeio/arktype/issues/1188#issuecomment-2614501752
papy0977
papy0977OP2w ago
Yes with jitless: true it works, thank you !

Did you find this page helpful?