@danypops/vehicle-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/README.md CHANGED
@@ -10,6 +10,6 @@ bun add @danypops/vehicle-core
10
10
 
11
11
  `defineVehicleOperation()`/`bindVehicleOperation()` build a serializable
12
12
  descriptor kept separate from its executable handler. See the
13
- [workspace README](https://github.com/DanyPops/daemon-kit#readme) for how it
13
+ [workspace README](https://github.com/DanyPops/vehicle#readme) for how it
14
14
  fits with `@danypops/vehicle-server`, `@danypops/vehicle-client`, and
15
15
  `@danypops/vehicle-client-pi`.
@@ -19,6 +19,21 @@ export interface VehicleSchemaCodec<T> {
19
19
  safeParse(value: unknown): VehicleSchemaResult<T>;
20
20
  }
21
21
  export declare function defineVehicleSchema<T>(codec: VehicleSchemaCodec<T>): VehicleSchemaCodec<T>;
22
+ export interface LooseObjectProperty {
23
+ readonly type: string;
24
+ readonly enum?: readonly string[];
25
+ }
26
+ /**
27
+ * A VehicleRegistry only ever calls a schema's own safeParse -- jsonSchema is
28
+ * descriptive metadata surfaced to a client/Pi projection, never itself
29
+ * enforced at runtime -- so a declared `enum` has to be checked here for
30
+ * real, or it's a documentation gesture, not an honest contract. Every
31
+ * consumer projecting a plain-object input onto a VehicleOperation needs the
32
+ * same required/enum checks; this is that check written once.
33
+ */
34
+ export declare function defineLooseObjectSchema(properties: Record<string, LooseObjectProperty>, required?: readonly string[]): VehicleSchemaCodec<Record<string, unknown>>;
35
+ /** Accepts any value unvalidated -- for an operation whose output shape isn't worth a dedicated schema (an internal/low-stakes result, or one already validated upstream by the domain logic it wraps). */
36
+ export declare const passthroughVehicleSchema: VehicleSchemaCodec<unknown>;
22
37
  export type VehicleEffect = "read" | "local-write" | "external-write" | "destructive" | "open-world";
23
38
  export type VehicleIdempotency = {
24
39
  readonly mode: "safe";
@@ -4,6 +4,46 @@ export function defineVehicleSchema(codec) {
4
4
  safeParse: codec.safeParse,
5
5
  });
6
6
  }
7
+ /**
8
+ * A VehicleRegistry only ever calls a schema's own safeParse -- jsonSchema is
9
+ * descriptive metadata surfaced to a client/Pi projection, never itself
10
+ * enforced at runtime -- so a declared `enum` has to be checked here for
11
+ * real, or it's a documentation gesture, not an honest contract. Every
12
+ * consumer projecting a plain-object input onto a VehicleOperation needs the
13
+ * same required/enum checks; this is that check written once.
14
+ */
15
+ export function defineLooseObjectSchema(properties, required = []) {
16
+ return defineVehicleSchema({
17
+ // LooseObjectProperty's named fields (type, enum) are all JSON-value-shaped
18
+ // at runtime, but TypeScript's structural check against the recursive
19
+ // JsonValue union doesn't see that through a plain interface -- the cast
20
+ // is a type-system limitation, not a runtime concern.
21
+ jsonSchema: { type: "object", properties: properties, required: [...required], additionalProperties: false },
22
+ safeParse(value) {
23
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
24
+ return { success: false, issues: [{ path: [], message: "input must be an object" }] };
25
+ }
26
+ const input = value;
27
+ for (const key of required) {
28
+ if (!(key in input))
29
+ return { success: false, issues: [{ path: [key], message: `${key} is required` }] };
30
+ }
31
+ for (const [key, schema] of Object.entries(properties)) {
32
+ if (!schema.enum || !(key in input))
33
+ continue;
34
+ if (!schema.enum.includes(input[key])) {
35
+ return { success: false, issues: [{ path: [key], message: `${key} must be one of ${schema.enum.join(", ")}` }] };
36
+ }
37
+ }
38
+ return { success: true, value: input };
39
+ },
40
+ });
41
+ }
42
+ /** Accepts any value unvalidated -- for an operation whose output shape isn't worth a dedicated schema (an internal/low-stakes result, or one already validated upstream by the domain logic it wraps). */
43
+ export const passthroughVehicleSchema = defineVehicleSchema({
44
+ jsonSchema: { type: "object" },
45
+ safeParse: (value) => ({ success: true, value }),
46
+ });
7
47
  export function defineVehicleOperation(options) {
8
48
  validateOperationMetadata(options);
9
49
  const descriptor = Object.freeze({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/vehicle-core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Vehicle's runtime-neutral wire contract: operation descriptors, schema codecs, failure shapes. Zero runtime dependencies, zero Bun-specific code -- the one thing every Vehicle client and server package depends on.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
26
- "url": "git+https://github.com/DanyPops/daemon-kit.git",
26
+ "url": "git+https://github.com/DanyPops/vehicle.git",
27
27
  "directory": "packages/vehicle-core"
28
28
  },
29
29
  "keywords": ["vehicle", "agent-tools"],
@@ -23,6 +23,51 @@ export function defineVehicleSchema<T>(codec: VehicleSchemaCodec<T>): VehicleSch
23
23
  });
24
24
  }
25
25
 
26
+ export interface LooseObjectProperty {
27
+ readonly type: string;
28
+ readonly enum?: readonly string[];
29
+ }
30
+
31
+ /**
32
+ * A VehicleRegistry only ever calls a schema's own safeParse -- jsonSchema is
33
+ * descriptive metadata surfaced to a client/Pi projection, never itself
34
+ * enforced at runtime -- so a declared `enum` has to be checked here for
35
+ * real, or it's a documentation gesture, not an honest contract. Every
36
+ * consumer projecting a plain-object input onto a VehicleOperation needs the
37
+ * same required/enum checks; this is that check written once.
38
+ */
39
+ export function defineLooseObjectSchema(properties: Record<string, LooseObjectProperty>, required: readonly string[] = []): VehicleSchemaCodec<Record<string, unknown>> {
40
+ return defineVehicleSchema<Record<string, unknown>>({
41
+ // LooseObjectProperty's named fields (type, enum) are all JSON-value-shaped
42
+ // at runtime, but TypeScript's structural check against the recursive
43
+ // JsonValue union doesn't see that through a plain interface -- the cast
44
+ // is a type-system limitation, not a runtime concern.
45
+ jsonSchema: { type: "object", properties: properties as unknown as JsonValue, required: [...required], additionalProperties: false },
46
+ safeParse(value) {
47
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
48
+ return { success: false, issues: [{ path: [], message: "input must be an object" }] };
49
+ }
50
+ const input = value as Record<string, unknown>;
51
+ for (const key of required) {
52
+ if (!(key in input)) return { success: false, issues: [{ path: [key], message: `${key} is required` }] };
53
+ }
54
+ for (const [key, schema] of Object.entries(properties)) {
55
+ if (!schema.enum || !(key in input)) continue;
56
+ if (!schema.enum.includes(input[key] as string)) {
57
+ return { success: false, issues: [{ path: [key], message: `${key} must be one of ${schema.enum.join(", ")}` }] };
58
+ }
59
+ }
60
+ return { success: true, value: input };
61
+ },
62
+ });
63
+ }
64
+
65
+ /** Accepts any value unvalidated -- for an operation whose output shape isn't worth a dedicated schema (an internal/low-stakes result, or one already validated upstream by the domain logic it wraps). */
66
+ export const passthroughVehicleSchema: VehicleSchemaCodec<unknown> = defineVehicleSchema<unknown>({
67
+ jsonSchema: { type: "object" },
68
+ safeParse: (value) => ({ success: true, value }),
69
+ });
70
+
26
71
  export type VehicleEffect = "read" | "local-write" | "external-write" | "destructive" | "open-world";
27
72
 
28
73
  export type VehicleIdempotency =