@sixb/core 0.0.1 → 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/README.md +24 -4
- package/dist/actions/index.js +17 -17
- package/dist/actions/worker.js +7 -7
- package/dist/actions/worker.js.map +1 -1
- package/dist/agents/adapters.d.ts.map +1 -1
- package/dist/agents/builders.d.ts +8 -1
- package/dist/agents/builders.d.ts.map +1 -1
- package/dist/agents/context.js +2 -2
- package/dist/agents/errors.d.ts +15 -0
- package/dist/agents/errors.d.ts.map +1 -1
- package/dist/agents/index.d.ts +4 -4
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +30 -11
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/tool-definition.d.ts +9 -0
- package/dist/agents/tool-definition.d.ts.map +1 -0
- package/dist/agents/types.d.ts +64 -0
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/agents/validation.d.ts +14 -1
- package/dist/agents/validation.d.ts.map +1 -1
- package/dist/auth/index.js +4 -4
- package/dist/authorization/index.js +3 -3
- package/dist/blob-storage/browser.js +3 -2
- package/dist/blob-storage/browser.js.map +1 -1
- package/dist/blob-storage/index.js +4 -3
- package/dist/blob-storage/index.js.map +1 -1
- package/dist/blob-storage/validation.d.ts.map +1 -1
- package/dist/bootstrap/index.js +25 -25
- package/dist/broker/index.js +2 -2
- package/dist/{chunk-590dejbp.js → chunk-4hwxq5e6.js} +1 -1
- package/dist/{chunk-wj01s5hr.js → chunk-4tvj3ate.js} +1 -1
- package/dist/{chunk-tsk2bxs3.js → chunk-53s4asfm.js} +13 -13
- package/dist/{chunk-tsk2bxs3.js.map → chunk-53s4asfm.js.map} +3 -3
- package/dist/{chunk-8r8cw2xw.js → chunk-63te770v.js} +40 -5
- package/dist/{chunk-8r8cw2xw.js.map → chunk-63te770v.js.map} +3 -3
- package/dist/{chunk-vj723r3p.js → chunk-6rzdtvn5.js} +1 -1
- package/dist/{chunk-5yny9z0e.js → chunk-6zrpewy4.js} +1 -1
- package/dist/{chunk-nvamz5dv.js → chunk-86459jn0.js} +294 -22
- package/dist/chunk-86459jn0.js.map +22 -0
- package/dist/{chunk-p6r5g77m.js → chunk-8kv2sez0.js} +1 -1
- package/dist/{chunk-k41ajtz7.js → chunk-9k3hgv8h.js} +1 -1
- package/dist/{chunk-jdwx7vzs.js → chunk-avppcgt6.js} +7 -7
- package/dist/{chunk-jdwx7vzs.js.map → chunk-avppcgt6.js.map} +1 -1
- package/dist/{chunk-2gpr6jsz.js → chunk-b1bqvhpe.js} +2 -2
- package/dist/{chunk-pv1yad91.js → chunk-b5dp7gaf.js} +17 -90
- package/dist/{chunk-pv1yad91.js.map → chunk-b5dp7gaf.js.map} +5 -6
- package/dist/{chunk-1vffvydj.js → chunk-be9vm29v.js} +6 -6
- package/dist/chunk-be9vm29v.js.map +10 -0
- package/dist/{chunk-wxhdnwh4.js → chunk-bh8bwma3.js} +1 -1
- package/dist/{chunk-0psb6kdw.js → chunk-btcsywk4.js} +12 -16
- package/dist/{chunk-0psb6kdw.js.map → chunk-btcsywk4.js.map} +3 -3
- package/dist/{chunk-0fygrz37.js → chunk-exrfbt2d.js} +1 -1
- package/dist/{chunk-c3zd880g.js → chunk-f6kyyeef.js} +2 -2
- package/dist/{chunk-q4qzq0ea.js → chunk-hb9xcx9c.js} +3 -3
- package/dist/{chunk-4pycxcj3.js → chunk-hz8m927w.js} +6 -9
- package/dist/{chunk-4pycxcj3.js.map → chunk-hz8m927w.js.map} +3 -3
- package/dist/{chunk-btpe64e4.js → chunk-m9w0artv.js} +1 -1
- package/dist/{chunk-45tw90k7.js → chunk-md9jbrm9.js} +1 -1
- package/dist/{chunk-dhmj53mx.js → chunk-nbywkytr.js} +3 -3
- package/dist/{chunk-t358gywy.js → chunk-p7vj8xgb.js} +8 -8
- package/dist/{chunk-2s6sydsh.js → chunk-qzyemcj7.js} +294 -4
- package/dist/chunk-qzyemcj7.js.map +13 -0
- package/dist/{chunk-r65yscvk.js → chunk-r82k4z7z.js} +131 -13
- package/dist/chunk-r82k4z7z.js.map +13 -0
- package/dist/{chunk-aebzcsxr.js → chunk-raq0x6bd.js} +11 -9
- package/dist/{chunk-aebzcsxr.js.map → chunk-raq0x6bd.js.map} +4 -4
- package/dist/{chunk-hsv43v58.js → chunk-rgzed50y.js} +77 -22
- package/dist/chunk-rgzed50y.js.map +22 -0
- package/dist/{chunk-yvef9xwc.js → chunk-rjqm6ksg.js} +7 -7
- package/dist/{chunk-f2b5e2gf.js → chunk-sb0shdvv.js} +18 -3
- package/dist/{chunk-f2b5e2gf.js.map → chunk-sb0shdvv.js.map} +3 -3
- package/dist/{chunk-dcgrzj1t.js → chunk-xt6qx9sh.js} +1 -1
- package/dist/{chunk-mx84y05q.js → chunk-y2ygpd0m.js} +2 -2
- package/dist/{chunk-yxss87qa.js → chunk-yn947ck3.js} +1 -1
- package/dist/datasets/builders.d.ts +18 -5
- package/dist/datasets/builders.d.ts.map +1 -1
- package/dist/datasets/changes.d.ts +18 -0
- package/dist/datasets/changes.d.ts.map +1 -0
- package/dist/datasets/index.d.ts +3 -1
- package/dist/datasets/index.d.ts.map +1 -1
- package/dist/datasets/types.d.ts +2 -0
- package/dist/datasets/types.d.ts.map +1 -1
- package/dist/datasets/validation.d.ts.map +1 -1
- package/dist/events/index.js +2 -2
- package/dist/http/api-routes.d.ts.map +1 -1
- package/dist/http/index.js +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +64 -58
- package/dist/index.js.map +1 -1
- package/dist/json.d.ts +8 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/lake-storage/definition-updates.d.ts.map +1 -1
- package/dist/lake-storage/in-memory.d.ts +15 -1
- package/dist/lake-storage/in-memory.d.ts.map +1 -1
- package/dist/lake-storage/index.d.ts +3 -1
- package/dist/lake-storage/index.d.ts.map +1 -1
- package/dist/lake-storage/index.js +15 -7
- package/dist/lake-storage/index.js.map +1 -1
- package/dist/lake-storage/merge-validation.d.ts +10 -0
- package/dist/lake-storage/merge-validation.d.ts.map +1 -0
- package/dist/lake-storage/merge.d.ts +32 -0
- package/dist/lake-storage/merge.d.ts.map +1 -0
- package/dist/lake-storage/types.d.ts +3 -1
- package/dist/lake-storage/types.d.ts.map +1 -1
- package/dist/logging/index.js +3 -3
- package/dist/logging/types.js +2 -2
- package/dist/materialization/index.js +2 -2
- package/dist/materializer/index.js +15 -15
- package/dist/materializer/index.js.map +1 -1
- package/dist/objects/index.js +17 -17
- package/dist/objects/query/index.js +8 -8
- package/dist/ontology/index.js +5 -5
- package/dist/ontology/internal.js +7 -6
- package/dist/ontology/internal.js.map +3 -3
- package/dist/ontology/validation/normalize.d.ts.map +1 -1
- package/dist/projections/internal.js +4 -4
- package/dist/runtime/sixb.d.ts.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +16 -16
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/ontology/provider.js +4 -4
- package/dist/storage/sync-runs/index.d.ts +1 -1
- package/dist/storage/sync-runs/index.d.ts.map +1 -1
- package/dist/storage/sync-runs/types.d.ts +7 -3
- package/dist/storage/sync-runs/types.d.ts.map +1 -1
- package/dist/storage/types.d.ts +1 -1
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/workflow-runs/in-memory.d.ts.map +1 -1
- package/dist/storage/workflow-runs/types.d.ts +2 -0
- package/dist/storage/workflow-runs/types.d.ts.map +1 -1
- package/dist/syncs/builders.d.ts +11 -3
- package/dist/syncs/builders.d.ts.map +1 -1
- package/dist/syncs/index.d.ts +1 -1
- package/dist/syncs/index.d.ts.map +1 -1
- package/dist/syncs/types.d.ts +24 -19
- package/dist/syncs/types.d.ts.map +1 -1
- package/dist/syncs/validation.d.ts +14 -0
- package/dist/syncs/validation.d.ts.map +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +837 -583
- package/dist/testing/index.js.map +5 -4
- package/dist/testing/lake-merge-storage-contract.d.ts +7 -0
- package/dist/testing/lake-merge-storage-contract.d.ts.map +1 -0
- package/dist/testing/lake-storage-contract.d.ts.map +1 -1
- package/dist/workflows/index.js +23 -23
- package/package.json +1 -1
- package/src/agents/adapters.ts +1 -9
- package/src/agents/builders.ts +53 -3
- package/src/agents/errors.ts +22 -0
- package/src/agents/index.ts +22 -2
- package/src/agents/prompt.ts +2 -2
- package/src/agents/tool-definition.ts +93 -0
- package/src/agents/types.ts +101 -0
- package/src/agents/validation.ts +287 -3
- package/src/blob-storage/validation.ts +2 -10
- package/src/datasets/builders.ts +49 -5
- package/src/datasets/changes.ts +13 -0
- package/src/datasets/index.ts +3 -0
- package/src/datasets/types.ts +5 -0
- package/src/datasets/validation.ts +53 -11
- package/src/http/api-routes.ts +39 -4
- package/src/index.ts +18 -1
- package/src/json.ts +11 -4
- package/src/lake-storage/definition-updates.ts +21 -0
- package/src/lake-storage/in-memory.ts +284 -2
- package/src/lake-storage/index.ts +14 -0
- package/src/lake-storage/merge-validation.ts +117 -0
- package/src/lake-storage/merge.ts +35 -0
- package/src/lake-storage/types.ts +3 -1
- package/src/ontology/json-schema.ts +2 -1
- package/src/ontology/validation/normalize.ts +9 -15
- package/src/runtime/sixb.ts +16 -1
- package/src/storage/index.ts +1 -0
- package/src/storage/sync-runs/in-memory.ts +2 -2
- package/src/storage/sync-runs/index.ts +1 -0
- package/src/storage/sync-runs/types.ts +9 -4
- package/src/storage/types.ts +1 -0
- package/src/storage/workflow-runs/in-memory.ts +2 -0
- package/src/storage/workflow-runs/types.ts +2 -0
- package/src/syncs/builders.ts +31 -13
- package/src/syncs/index.ts +1 -0
- package/src/syncs/types.ts +44 -18
- package/src/syncs/validation.ts +77 -0
- package/src/testing/index.ts +4 -0
- package/src/testing/lake-merge-storage-contract.ts +302 -0
- package/src/testing/lake-storage-contract.ts +23 -0
- package/src/workflows/snapshot.ts +1 -10
- package/dist/chunk-1vffvydj.js.map +0 -10
- package/dist/chunk-2s6sydsh.js.map +0 -12
- package/dist/chunk-hsv43v58.js.map +0 -21
- package/dist/chunk-nvamz5dv.js.map +0 -21
- package/dist/chunk-r65yscvk.js.map +0 -11
- /package/dist/{chunk-590dejbp.js.map → chunk-4hwxq5e6.js.map} +0 -0
- /package/dist/{chunk-wj01s5hr.js.map → chunk-4tvj3ate.js.map} +0 -0
- /package/dist/{chunk-vj723r3p.js.map → chunk-6rzdtvn5.js.map} +0 -0
- /package/dist/{chunk-5yny9z0e.js.map → chunk-6zrpewy4.js.map} +0 -0
- /package/dist/{chunk-p6r5g77m.js.map → chunk-8kv2sez0.js.map} +0 -0
- /package/dist/{chunk-k41ajtz7.js.map → chunk-9k3hgv8h.js.map} +0 -0
- /package/dist/{chunk-2gpr6jsz.js.map → chunk-b1bqvhpe.js.map} +0 -0
- /package/dist/{chunk-wxhdnwh4.js.map → chunk-bh8bwma3.js.map} +0 -0
- /package/dist/{chunk-0fygrz37.js.map → chunk-exrfbt2d.js.map} +0 -0
- /package/dist/{chunk-c3zd880g.js.map → chunk-f6kyyeef.js.map} +0 -0
- /package/dist/{chunk-q4qzq0ea.js.map → chunk-hb9xcx9c.js.map} +0 -0
- /package/dist/{chunk-btpe64e4.js.map → chunk-m9w0artv.js.map} +0 -0
- /package/dist/{chunk-45tw90k7.js.map → chunk-md9jbrm9.js.map} +0 -0
- /package/dist/{chunk-dhmj53mx.js.map → chunk-nbywkytr.js.map} +0 -0
- /package/dist/{chunk-t358gywy.js.map → chunk-p7vj8xgb.js.map} +0 -0
- /package/dist/{chunk-yvef9xwc.js.map → chunk-rjqm6ksg.js.map} +0 -0
- /package/dist/{chunk-dcgrzj1t.js.map → chunk-xt6qx9sh.js.map} +0 -0
- /package/dist/{chunk-mx84y05q.js.map → chunk-y2ygpd0m.js.map} +0 -0
- /package/dist/{chunk-yxss87qa.js.map → chunk-yn947ck3.js.map} +0 -0
package/src/agents/validation.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import { getInvalidJsonValueReason } from "../json"
|
|
1
|
+
import { getInvalidJsonValueReason, isPlainRecord, type JsonValue } from "../json"
|
|
2
|
+
import {
|
|
3
|
+
normalizeSchemaValue,
|
|
4
|
+
type ObjectSchema,
|
|
5
|
+
OntologyValidationError,
|
|
6
|
+
type ValueType,
|
|
7
|
+
validateSchemaValue,
|
|
8
|
+
} from "../ontology"
|
|
2
9
|
import type { GroupDefinition, SecurityRegistry } from "../security"
|
|
3
10
|
import { AgentDefinitionError } from "./errors"
|
|
4
11
|
import {
|
|
@@ -6,9 +13,26 @@ import {
|
|
|
6
13
|
type AgentDefinition,
|
|
7
14
|
type AgentLoopConfig,
|
|
8
15
|
type AgentReasoningLevel,
|
|
16
|
+
type AgentToolDefinition,
|
|
17
|
+
type AgentToolInputSchema,
|
|
9
18
|
type DefineAgentConfig,
|
|
10
19
|
} from "./types"
|
|
11
20
|
|
|
21
|
+
const AGENT_TOOL_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/
|
|
22
|
+
const AGENT_TOOL_PRIMITIVE_SCHEMAS = new Set([
|
|
23
|
+
"string",
|
|
24
|
+
"integer",
|
|
25
|
+
"double",
|
|
26
|
+
"decimal",
|
|
27
|
+
"boolean",
|
|
28
|
+
"date",
|
|
29
|
+
"timestamp",
|
|
30
|
+
"uuid",
|
|
31
|
+
"fileRef",
|
|
32
|
+
])
|
|
33
|
+
|
|
34
|
+
export const AGENT_RESERVED_TOOL_NAMES = ["bash"] as const
|
|
35
|
+
|
|
12
36
|
export function assertNonEmpty(value: string, field: string): void {
|
|
13
37
|
if (!value.trim()) {
|
|
14
38
|
throw new AgentDefinitionError(`[Sixb] Agent ${field} must not be empty.`)
|
|
@@ -24,10 +48,117 @@ export function isAgentDefinition(value: unknown): value is AgentDefinition {
|
|
|
24
48
|
typeof value.instructions === "string" &&
|
|
25
49
|
(value.reasoning === undefined || isAgentReasoningLevel(value.reasoning)) &&
|
|
26
50
|
(value.providerOptions === undefined || isProviderOptions(value.providerOptions)) &&
|
|
27
|
-
Array.isArray(value.groupIds)
|
|
51
|
+
Array.isArray(value.groupIds) &&
|
|
52
|
+
isAgentToolDefinitionArray(value.tools)
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isAgentToolDefinition(value: unknown): value is AgentToolDefinition {
|
|
57
|
+
return (
|
|
58
|
+
isRecord(value) &&
|
|
59
|
+
value.kind === "agentTool" &&
|
|
60
|
+
typeof value.name === "string" &&
|
|
61
|
+
typeof value.description === "string" &&
|
|
62
|
+
isPlainRecord(value.input) &&
|
|
63
|
+
typeof value.handler === "function"
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function assertValidAgentToolName(name: unknown): asserts name is string {
|
|
68
|
+
if (typeof name !== "string" || !AGENT_TOOL_NAME_PATTERN.test(name)) {
|
|
69
|
+
throw new AgentDefinitionError(
|
|
70
|
+
"[Sixb] Agent tool name must be 1-64 characters, start with a letter or underscore, and contain only letters, numbers, underscores, or hyphens."
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
if ((AGENT_RESERVED_TOOL_NAMES as readonly string[]).includes(name)) {
|
|
74
|
+
throw new AgentDefinitionError(`[Sixb] Agent tool name '${name}' is reserved by the framework.`)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function assertValidAgentToolDescription(name: string, description: unknown): void {
|
|
79
|
+
if (typeof description !== "string" || !description.trim()) {
|
|
80
|
+
throw new AgentDefinitionError(`[Sixb] Agent tool '${name}' description must not be empty.`)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function assertValidAgentToolInput(name: string, input: unknown): void {
|
|
85
|
+
if (!isPlainRecord(input)) {
|
|
86
|
+
throw new AgentDefinitionError(`[Sixb] Agent tool '${name}' input must be a schema object.`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const [field, schema] of Object.entries(input)) {
|
|
90
|
+
if (!field.trim()) {
|
|
91
|
+
throw new AgentDefinitionError(
|
|
92
|
+
`[Sixb] Agent tool '${name}' input field names must not be empty.`
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
assertValidAgentToolSchema(name, schema, `input.${field}`, new Set())
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function validateAndSnapshotAgentToolInput<TInput extends AgentToolInputSchema>(
|
|
100
|
+
toolName: string,
|
|
101
|
+
input: TInput
|
|
102
|
+
): TInput {
|
|
103
|
+
assertValidAgentToolInput(toolName, input)
|
|
104
|
+
try {
|
|
105
|
+
return deepFreeze(structuredClone(input))
|
|
106
|
+
} catch {
|
|
107
|
+
throw new AgentDefinitionError(
|
|
108
|
+
`[Sixb] Agent tool '${toolName}' input must be safely snapshot-compatible.`
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Validate model input against a tool schema, normalize it to JSON, and lock the snapshot. */
|
|
114
|
+
export function validateAndNormalizeAgentToolInput(
|
|
115
|
+
toolName: string,
|
|
116
|
+
shape: AgentToolInputSchema,
|
|
117
|
+
value: unknown,
|
|
118
|
+
valueTypesById: ReadonlyMap<string, ValueType>
|
|
119
|
+
): Record<string, JsonValue> {
|
|
120
|
+
const label = `Agent tool '${toolName}' input`
|
|
121
|
+
if (!isPlainRecord(value)) {
|
|
122
|
+
throw new OntologyValidationError(`[Sixb] ${label} must be a JSON object.`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const reason = getInvalidJsonValueReason(value, label)
|
|
126
|
+
if (reason) {
|
|
127
|
+
throw new OntologyValidationError(`[Sixb] ${label} must be JSON-compatible; ${reason}.`)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const schema: ObjectSchema = {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: Object.fromEntries(
|
|
133
|
+
Object.entries(shape).map(([field, fieldSchema]) => [
|
|
134
|
+
field,
|
|
135
|
+
{ schema: fieldSchema, required: true },
|
|
136
|
+
])
|
|
137
|
+
),
|
|
138
|
+
}
|
|
139
|
+
validateSchemaValue(schema, value, label, valueTypesById)
|
|
140
|
+
return deepFreeze(
|
|
141
|
+
normalizeSchemaValue(schema, value, label, valueTypesById) as Record<string, JsonValue>
|
|
28
142
|
)
|
|
29
143
|
}
|
|
30
144
|
|
|
145
|
+
/** Revalidate and lock tool definitions supplied directly to the runtime. */
|
|
146
|
+
export function validateAgentToolsAtStartup(agents: readonly AgentDefinition[]): void {
|
|
147
|
+
for (const agent of agents) {
|
|
148
|
+
if (!isAgentDefinition(agent)) {
|
|
149
|
+
throw new AgentDefinitionError("[Sixb] Agents must contain only valid agent definitions.")
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
assertValidAgentToolDefinitions(agent.id, agent.tools)
|
|
153
|
+
for (const tool of agent.tools) {
|
|
154
|
+
deepFreeze(tool.input)
|
|
155
|
+
Object.freeze(tool)
|
|
156
|
+
}
|
|
157
|
+
Object.freeze(agent.tools)
|
|
158
|
+
Object.freeze(agent)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
31
162
|
export function assertValidLoopConfig(loop: AgentLoopConfig | undefined): void {
|
|
32
163
|
const maxSteps = loop?.stopWhen?.maxSteps
|
|
33
164
|
if (maxSteps === undefined) {
|
|
@@ -96,7 +227,7 @@ export function groupIdsFromDefinitions(
|
|
|
96
227
|
})
|
|
97
228
|
|
|
98
229
|
assertNoDuplicateGroupIds(agentId, groupIds)
|
|
99
|
-
return groupIds
|
|
230
|
+
return Object.freeze(groupIds)
|
|
100
231
|
}
|
|
101
232
|
|
|
102
233
|
export function validateAgentGroupReferences(
|
|
@@ -146,6 +277,159 @@ function isProviderOptions(
|
|
|
146
277
|
)
|
|
147
278
|
}
|
|
148
279
|
|
|
280
|
+
function assertValidAgentToolSchema(
|
|
281
|
+
toolName: string,
|
|
282
|
+
schema: unknown,
|
|
283
|
+
path: string,
|
|
284
|
+
visiting: Set<object>
|
|
285
|
+
): void {
|
|
286
|
+
if (typeof schema === "string") {
|
|
287
|
+
if (AGENT_TOOL_PRIMITIVE_SCHEMAS.has(schema)) {
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
throw invalidAgentToolSchema(toolName, path)
|
|
291
|
+
}
|
|
292
|
+
if (!isPlainRecord(schema) || typeof schema.type !== "string" || visiting.has(schema)) {
|
|
293
|
+
throw invalidAgentToolSchema(toolName, path)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
visiting.add(schema)
|
|
297
|
+
switch (schema.type) {
|
|
298
|
+
case "enum": {
|
|
299
|
+
const values = schema.values
|
|
300
|
+
const validValues = isValidAgentToolEnumValues(values, schema.valueType)
|
|
301
|
+
if (!validValues || new Set(values).size !== values.length) {
|
|
302
|
+
throw invalidAgentToolSchema(toolName, path)
|
|
303
|
+
}
|
|
304
|
+
break
|
|
305
|
+
}
|
|
306
|
+
case "array":
|
|
307
|
+
assertValidAgentToolSchema(toolName, schema.items, `${path}.items`, visiting)
|
|
308
|
+
break
|
|
309
|
+
case "map":
|
|
310
|
+
if (schema.keySchema !== "string") {
|
|
311
|
+
throw invalidAgentToolSchema(toolName, path)
|
|
312
|
+
}
|
|
313
|
+
assertValidAgentToolSchema(toolName, schema.valueSchema, `${path}.valueSchema`, visiting)
|
|
314
|
+
break
|
|
315
|
+
case "object": {
|
|
316
|
+
if (!isPlainRecord(schema.properties)) {
|
|
317
|
+
throw invalidAgentToolSchema(toolName, path)
|
|
318
|
+
}
|
|
319
|
+
for (const [fieldId, field] of Object.entries(schema.properties)) {
|
|
320
|
+
if (!fieldId.trim() || !isPlainRecord(field) || !("schema" in field)) {
|
|
321
|
+
throw invalidAgentToolSchema(toolName, `${path}.properties.${fieldId}`)
|
|
322
|
+
}
|
|
323
|
+
if (
|
|
324
|
+
(field.required !== undefined && typeof field.required !== "boolean") ||
|
|
325
|
+
(field.nullable !== undefined && typeof field.nullable !== "boolean") ||
|
|
326
|
+
(field.description !== undefined &&
|
|
327
|
+
(typeof field.description !== "string" || !field.description.trim()))
|
|
328
|
+
) {
|
|
329
|
+
throw invalidAgentToolSchema(toolName, `${path}.properties.${fieldId}`)
|
|
330
|
+
}
|
|
331
|
+
assertValidAgentToolSchema(
|
|
332
|
+
toolName,
|
|
333
|
+
field.schema,
|
|
334
|
+
`${path}.properties.${fieldId}.schema`,
|
|
335
|
+
visiting
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
break
|
|
339
|
+
}
|
|
340
|
+
case "valueTypeRef":
|
|
341
|
+
if (typeof schema.valueTypeId !== "string" || !schema.valueTypeId.trim()) {
|
|
342
|
+
throw invalidAgentToolSchema(toolName, path)
|
|
343
|
+
}
|
|
344
|
+
if (schema._resolved !== undefined) {
|
|
345
|
+
assertValidAgentToolSchema(toolName, schema._resolved, `${path}._resolved`, visiting)
|
|
346
|
+
}
|
|
347
|
+
break
|
|
348
|
+
default:
|
|
349
|
+
throw invalidAgentToolSchema(toolName, path)
|
|
350
|
+
}
|
|
351
|
+
visiting.delete(schema)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function invalidAgentToolSchema(toolName: string, path: string): AgentDefinitionError {
|
|
355
|
+
return new AgentDefinitionError(
|
|
356
|
+
`[Sixb] Agent tool '${toolName}' ${path} must be a valid Sixb schema.`
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function isAgentToolDefinitionArray(value: unknown): value is readonly AgentToolDefinition[] {
|
|
361
|
+
if (!Array.isArray(value)) {
|
|
362
|
+
return false
|
|
363
|
+
}
|
|
364
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
365
|
+
if (!Object.hasOwn(value, index) || !isAgentToolDefinition(value[index])) {
|
|
366
|
+
return false
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return true
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function assertValidAgentToolDefinitions(
|
|
373
|
+
agentId: string,
|
|
374
|
+
tools: readonly AgentToolDefinition[]
|
|
375
|
+
): void {
|
|
376
|
+
const seen = new Set<string>()
|
|
377
|
+
for (let index = 0; index < tools.length; index += 1) {
|
|
378
|
+
const tool = tools[index]
|
|
379
|
+
if (!Object.hasOwn(tools, index) || !isAgentToolDefinition(tool)) {
|
|
380
|
+
throw new AgentDefinitionError(
|
|
381
|
+
`[Sixb] Agent '${agentId}' tools must contain only agent tool definitions.`
|
|
382
|
+
)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
assertValidAgentToolName(tool.name)
|
|
386
|
+
assertValidAgentToolDescription(tool.name, tool.description)
|
|
387
|
+
assertValidAgentToolInput(tool.name, tool.input)
|
|
388
|
+
if (seen.has(tool.name)) {
|
|
389
|
+
throw new AgentDefinitionError(
|
|
390
|
+
`[Sixb] Agent '${agentId}' tools contains duplicate tool name '${tool.name}'.`
|
|
391
|
+
)
|
|
392
|
+
}
|
|
393
|
+
seen.add(tool.name)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function isValidAgentToolEnumValues(values: unknown, valueType: unknown): values is unknown[] {
|
|
398
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
399
|
+
return false
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
403
|
+
if (!Object.hasOwn(values, index)) {
|
|
404
|
+
return false
|
|
405
|
+
}
|
|
406
|
+
const value = values[index]
|
|
407
|
+
if (
|
|
408
|
+
valueType === "string"
|
|
409
|
+
? typeof value !== "string"
|
|
410
|
+
: valueType !== "integer" || typeof value !== "number" || !Number.isInteger(value)
|
|
411
|
+
) {
|
|
412
|
+
return false
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return true
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function deepFreeze<T>(value: T, seen: Set<object> = new Set()): T {
|
|
419
|
+
if (value === null || typeof value !== "object" || seen.has(value)) {
|
|
420
|
+
return value
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
seen.add(value)
|
|
424
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
425
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key)
|
|
426
|
+
if (descriptor && "value" in descriptor) {
|
|
427
|
+
deepFreeze(descriptor.value, seen)
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return Object.freeze(value)
|
|
431
|
+
}
|
|
432
|
+
|
|
149
433
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
150
434
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
151
435
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPlainRecord } from "../json"
|
|
1
2
|
import { blobIdFromDigest } from "./derive"
|
|
2
3
|
import type {
|
|
3
4
|
BlobDigest,
|
|
@@ -7,22 +8,13 @@ import type {
|
|
|
7
8
|
RangeReadableBlobStorage,
|
|
8
9
|
} from "./types"
|
|
9
10
|
|
|
10
|
-
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
11
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
12
|
-
return false
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const prototype = Object.getPrototypeOf(value)
|
|
16
|
-
return prototype === Object.prototype || prototype === null
|
|
17
|
-
}
|
|
18
|
-
|
|
19
11
|
export function isBlobDigest(value: unknown): value is BlobDigest {
|
|
20
12
|
return typeof value === "string" && value.startsWith("sha256:") && value.length > "sha256:".length
|
|
21
13
|
}
|
|
22
14
|
|
|
23
15
|
export function isFileRef(value: unknown): value is FileRef {
|
|
24
16
|
return (
|
|
25
|
-
|
|
17
|
+
isPlainRecord(value) &&
|
|
26
18
|
typeof value.blobId === "string" &&
|
|
27
19
|
value.blobId.trim().length > 0 &&
|
|
28
20
|
isBlobDigest(value.digest) &&
|
package/src/datasets/builders.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
DatasetColumnNameOf,
|
|
5
5
|
DatasetColumnType,
|
|
6
6
|
DatasetDefinition,
|
|
7
|
+
DatasetPrimaryKey,
|
|
7
8
|
} from "./types"
|
|
8
9
|
import { assertDatasetColumnDefinition, assertDatasetDefinition } from "./validation"
|
|
9
10
|
|
|
@@ -13,6 +14,7 @@ type DatasetColumnOptions = {
|
|
|
13
14
|
|
|
14
15
|
type DefineDatasetOptions = {
|
|
15
16
|
readonly schema: readonly DatasetColumnDefinition[]
|
|
17
|
+
readonly primaryKey?: DatasetPrimaryKey
|
|
16
18
|
readonly partitionBy?: readonly string[]
|
|
17
19
|
readonly description?: string
|
|
18
20
|
}
|
|
@@ -20,6 +22,7 @@ type DefineDatasetOptions = {
|
|
|
20
22
|
type DeriveDatasetOptions<TParent extends DatasetDefinition> = {
|
|
21
23
|
readonly pick?: readonly DatasetColumnNameOf<TParent>[]
|
|
22
24
|
readonly add?: readonly DatasetColumnDefinition[]
|
|
25
|
+
readonly primaryKey?: DatasetPrimaryKey
|
|
23
26
|
readonly partitionBy?: readonly string[]
|
|
24
27
|
readonly description?: string
|
|
25
28
|
}
|
|
@@ -39,6 +42,24 @@ type ColumnDefinitionOf<
|
|
|
39
42
|
TName extends ColumnNameOf<TColumns>,
|
|
40
43
|
> = Extract<TColumns[number], { readonly name: TName }>
|
|
41
44
|
|
|
45
|
+
type PrimaryKeyColumnNameOf<TColumns extends readonly DatasetColumnDefinition[]> =
|
|
46
|
+
string extends ColumnNameOf<TColumns>
|
|
47
|
+
? string
|
|
48
|
+
: {
|
|
49
|
+
[TName in ColumnNameOf<TColumns>]: ColumnDefinitionOf<
|
|
50
|
+
TColumns,
|
|
51
|
+
TName
|
|
52
|
+
>["type"] extends "string"
|
|
53
|
+
? ColumnDefinitionOf<TColumns, TName> extends { readonly nullable: true }
|
|
54
|
+
? never
|
|
55
|
+
: TName
|
|
56
|
+
: never
|
|
57
|
+
}[ColumnNameOf<TColumns>]
|
|
58
|
+
|
|
59
|
+
type CheckedDefineDatasetOptions<TOptions extends DefineDatasetOptions> = TOptions & {
|
|
60
|
+
readonly primaryKey?: DatasetPrimaryKey<PrimaryKeyColumnNameOf<TOptions["schema"]>>
|
|
61
|
+
}
|
|
62
|
+
|
|
42
63
|
type PickColumns<
|
|
43
64
|
TColumns extends readonly DatasetColumnDefinition[],
|
|
44
65
|
TNames extends readonly ColumnNameOf<TColumns>[],
|
|
@@ -86,8 +107,9 @@ type DatasetColumnResult<
|
|
|
86
107
|
|
|
87
108
|
type DatasetDefinitionResult<TId extends string, TOptions extends DefineDatasetOptions> = Omit<
|
|
88
109
|
DatasetDefinition<TId, TOptions["schema"]>,
|
|
89
|
-
"partitionBy" | "description"
|
|
110
|
+
"primaryKey" | "partitionBy" | "description"
|
|
90
111
|
> &
|
|
112
|
+
FieldFromOptions<TOptions, "primaryKey", DatasetPrimaryKey> &
|
|
91
113
|
FieldFromOptions<TOptions, "partitionBy", readonly string[]> &
|
|
92
114
|
FieldFromOptions<TOptions, "description", string>
|
|
93
115
|
|
|
@@ -95,10 +117,21 @@ type DerivedDatasetDefinitionResult<
|
|
|
95
117
|
TId extends string,
|
|
96
118
|
TParent extends DatasetDefinition,
|
|
97
119
|
TOptions extends DeriveDatasetOptions<TParent> | undefined,
|
|
98
|
-
> = Omit<
|
|
120
|
+
> = Omit<
|
|
121
|
+
DatasetDefinition<TId, DerivedColumns<TParent, TOptions>>,
|
|
122
|
+
"primaryKey" | "partitionBy" | "description"
|
|
123
|
+
> &
|
|
124
|
+
FieldFromOptions<TOptions, "primaryKey", DatasetPrimaryKey> &
|
|
99
125
|
FieldFromOptions<TOptions, "partitionBy", readonly string[]> &
|
|
100
126
|
FieldFromOptions<TOptions, "description", string>
|
|
101
127
|
|
|
128
|
+
type CheckedDeriveDatasetOptions<
|
|
129
|
+
TParent extends DatasetDefinition,
|
|
130
|
+
TOptions extends DeriveDatasetOptions<TParent>,
|
|
131
|
+
> = TOptions & {
|
|
132
|
+
readonly primaryKey?: DatasetPrimaryKey<PrimaryKeyColumnNameOf<DerivedColumns<TParent, TOptions>>>
|
|
133
|
+
}
|
|
134
|
+
|
|
102
135
|
export function col<const TName extends string, const TType extends DatasetColumnType>(
|
|
103
136
|
name: TName,
|
|
104
137
|
type: TType
|
|
@@ -130,12 +163,19 @@ function assertDatasetId(id: string): void {
|
|
|
130
163
|
}
|
|
131
164
|
|
|
132
165
|
function createDatasetDefinition(id: string, options: DefineDatasetOptions): DatasetDefinition {
|
|
133
|
-
const definition:
|
|
166
|
+
const definition: unknown = {
|
|
134
167
|
kind: "dataset",
|
|
135
168
|
id,
|
|
136
169
|
schema: {
|
|
137
170
|
columns: [...options.schema],
|
|
138
171
|
},
|
|
172
|
+
...(options.primaryKey !== undefined
|
|
173
|
+
? {
|
|
174
|
+
primaryKey: Array.isArray(options.primaryKey)
|
|
175
|
+
? [...options.primaryKey]
|
|
176
|
+
: options.primaryKey,
|
|
177
|
+
}
|
|
178
|
+
: {}),
|
|
139
179
|
...(options.partitionBy !== undefined ? { partitionBy: [...options.partitionBy] } : {}),
|
|
140
180
|
...(options.description !== undefined ? { description: options.description } : {}),
|
|
141
181
|
}
|
|
@@ -189,6 +229,7 @@ function deriveDatasetDefinition(
|
|
|
189
229
|
|
|
190
230
|
return createDatasetDefinition(id, {
|
|
191
231
|
schema: [...pickedColumns, ...(options.add ?? [])],
|
|
232
|
+
...(options.primaryKey !== undefined ? { primaryKey: options.primaryKey } : {}),
|
|
192
233
|
...(options.partitionBy !== undefined ? { partitionBy: options.partitionBy } : {}),
|
|
193
234
|
...(options.description !== undefined ? { description: options.description } : {}),
|
|
194
235
|
})
|
|
@@ -203,7 +244,10 @@ function createDatasetDeriveBuilder<TId extends string>(id: TId) {
|
|
|
203
244
|
function derive<
|
|
204
245
|
const TParent extends DatasetDefinition,
|
|
205
246
|
const TOptions extends DeriveDatasetOptions<TParent>,
|
|
206
|
-
>(
|
|
247
|
+
>(
|
|
248
|
+
parent: TParent,
|
|
249
|
+
options: CheckedDeriveDatasetOptions<TParent, TOptions>
|
|
250
|
+
): DerivedDatasetDefinitionResult<TId, TParent, TOptions>
|
|
207
251
|
function derive(
|
|
208
252
|
parent: DatasetDefinition,
|
|
209
253
|
options?: DeriveDatasetOptions<DatasetDefinition>
|
|
@@ -220,7 +264,7 @@ export function defineDataset<const TId extends string>(
|
|
|
220
264
|
export function defineDataset<
|
|
221
265
|
const TId extends string,
|
|
222
266
|
const TOptions extends DefineDatasetOptions,
|
|
223
|
-
>(id: TId, options: TOptions): DatasetDefinitionResult<TId, TOptions>
|
|
267
|
+
>(id: TId, options: CheckedDefineDatasetOptions<TOptions>): DatasetDefinitionResult<TId, TOptions>
|
|
224
268
|
export function defineDataset(
|
|
225
269
|
id: string,
|
|
226
270
|
options?: DefineDatasetOptions
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type MergeChange<Row, Key> =
|
|
2
|
+
| { readonly kind: "upsert"; readonly row: Row }
|
|
3
|
+
| { readonly kind: "delete"; readonly key: Key }
|
|
4
|
+
|
|
5
|
+
export const change = {
|
|
6
|
+
upsert<const Row extends object>(row: Row): { readonly kind: "upsert"; readonly row: Row } {
|
|
7
|
+
return { kind: "upsert", row }
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
delete<const Key extends object>(key: Key): { readonly kind: "delete"; readonly key: Key } {
|
|
11
|
+
return { kind: "delete", key }
|
|
12
|
+
},
|
|
13
|
+
}
|
package/src/datasets/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { col, defineDataset } from "./builders"
|
|
2
|
+
export type { MergeChange } from "./changes"
|
|
3
|
+
export { change } from "./changes"
|
|
2
4
|
export { DatasetValidationError } from "./errors"
|
|
3
5
|
export type {
|
|
4
6
|
DatasetColumnDefinition,
|
|
@@ -8,6 +10,7 @@ export type {
|
|
|
8
10
|
DatasetColumnTypeOf,
|
|
9
11
|
DatasetColumnUnionOf,
|
|
10
12
|
DatasetDefinition,
|
|
13
|
+
DatasetPrimaryKey,
|
|
11
14
|
DatasetSchema,
|
|
12
15
|
} from "./types"
|
|
13
16
|
export { getDatasetRowValidationError, isDatasetDefinition } from "./validation"
|
package/src/datasets/types.ts
CHANGED
|
@@ -24,6 +24,10 @@ export interface DatasetSchema<
|
|
|
24
24
|
readonly columns: TColumns
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export type DatasetPrimaryKey<TColumnName extends string = string> =
|
|
28
|
+
| TColumnName
|
|
29
|
+
| readonly [TColumnName, TColumnName, ...TColumnName[]]
|
|
30
|
+
|
|
27
31
|
export interface DatasetDefinition<
|
|
28
32
|
TId extends string = string,
|
|
29
33
|
TColumns extends readonly DatasetColumnDefinition[] = readonly DatasetColumnDefinition[],
|
|
@@ -31,6 +35,7 @@ export interface DatasetDefinition<
|
|
|
31
35
|
readonly kind: "dataset"
|
|
32
36
|
readonly id: TId
|
|
33
37
|
readonly schema: DatasetSchema<TColumns>
|
|
38
|
+
readonly primaryKey?: DatasetPrimaryKey
|
|
34
39
|
readonly partitionBy?: readonly string[]
|
|
35
40
|
readonly description?: string
|
|
36
41
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isFileRef } from "../blob-storage"
|
|
2
|
-
import { isJsonValue } from "../json"
|
|
2
|
+
import { isJsonValue, isPlainRecord } from "../json"
|
|
3
3
|
import { isDecimalString } from "../ontology/decimal"
|
|
4
4
|
import { DatasetValidationError } from "./errors"
|
|
5
5
|
import type {
|
|
@@ -25,15 +25,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
25
25
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
29
|
-
if (!isRecord(value)) {
|
|
30
|
-
return false
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const prototype = Object.getPrototypeOf(value)
|
|
34
|
-
return prototype === Object.prototype || prototype === null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
28
|
function isValidDateValue(value: unknown): boolean {
|
|
38
29
|
return value instanceof Date && !Number.isNaN(value.getTime())
|
|
39
30
|
}
|
|
@@ -128,6 +119,53 @@ function assertDatasetSchema(
|
|
|
128
119
|
}
|
|
129
120
|
}
|
|
130
121
|
|
|
122
|
+
function assertDatasetPrimaryKey(
|
|
123
|
+
primaryKey: unknown,
|
|
124
|
+
schema: DatasetSchema,
|
|
125
|
+
createError: (message: string) => Error
|
|
126
|
+
): void {
|
|
127
|
+
let keyColumns: readonly unknown[]
|
|
128
|
+
if (typeof primaryKey === "string") {
|
|
129
|
+
keyColumns = [primaryKey]
|
|
130
|
+
} else if (Array.isArray(primaryKey)) {
|
|
131
|
+
if (primaryKey.length < 2) {
|
|
132
|
+
throw createError(
|
|
133
|
+
"Dataset primaryKey arrays must contain at least two column names. Use a string for a single-column key."
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
keyColumns = primaryKey
|
|
137
|
+
} else {
|
|
138
|
+
throw createError(
|
|
139
|
+
"Dataset primaryKey must be a column name or an array of at least two column names."
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const columnsByName = new Map(schema.columns.map((column) => [column.name, column] as const))
|
|
144
|
+
const seenColumnNames = new Set<string>()
|
|
145
|
+
|
|
146
|
+
for (const columnName of keyColumns) {
|
|
147
|
+
if (typeof columnName !== "string" || columnName.trim().length === 0) {
|
|
148
|
+
throw createError("Dataset primaryKey columns must be non-empty strings.")
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (seenColumnNames.has(columnName)) {
|
|
152
|
+
throw createError(`Dataset primaryKey contains duplicate column '${columnName}'.`)
|
|
153
|
+
}
|
|
154
|
+
seenColumnNames.add(columnName)
|
|
155
|
+
|
|
156
|
+
const column = columnsByName.get(columnName)
|
|
157
|
+
if (column === undefined) {
|
|
158
|
+
throw createError(`Dataset primaryKey column '${columnName}' is not declared in the schema.`)
|
|
159
|
+
}
|
|
160
|
+
if (column.type !== "string") {
|
|
161
|
+
throw createError(`Dataset primaryKey column '${columnName}' must have type 'string'.`)
|
|
162
|
+
}
|
|
163
|
+
if (column.nullable === true) {
|
|
164
|
+
throw createError(`Dataset primaryKey column '${columnName}' must not be nullable.`)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
131
169
|
export function assertDatasetDefinition(
|
|
132
170
|
definition: unknown,
|
|
133
171
|
createError: (message: string) => Error = (message) => new DatasetValidationError(message)
|
|
@@ -143,6 +181,10 @@ export function assertDatasetDefinition(
|
|
|
143
181
|
assertNonEmptyString(definition.id, "Dataset id", createError)
|
|
144
182
|
assertDatasetSchema(definition.schema, createError)
|
|
145
183
|
|
|
184
|
+
if (definition.primaryKey !== undefined) {
|
|
185
|
+
assertDatasetPrimaryKey(definition.primaryKey, definition.schema, createError)
|
|
186
|
+
}
|
|
187
|
+
|
|
146
188
|
if (definition.partitionBy !== undefined) {
|
|
147
189
|
if (!Array.isArray(definition.partitionBy)) {
|
|
148
190
|
throw createError("Dataset partitionBy must be an array of column names.")
|
|
@@ -176,7 +218,7 @@ export function getDatasetRowValidationError(
|
|
|
176
218
|
dataset: DatasetDefinition,
|
|
177
219
|
options?: { readonly columns?: Iterable<string> }
|
|
178
220
|
): string | null {
|
|
179
|
-
if (!
|
|
221
|
+
if (!isPlainRecord(row)) {
|
|
180
222
|
return `Dataset '${dataset.id}' rows must be plain objects.`
|
|
181
223
|
}
|
|
182
224
|
|