@zeno-lib/schema 0.2.2 → 0.4.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/index.d.mts CHANGED
@@ -7,28 +7,134 @@ type NoTableSchemaOptions = {
7
7
  [noTableSchemaOptions]?: never;
8
8
  };
9
9
  type TableColumns<TTable extends Table> = TTable["_"]["columns"];
10
- type AuditColumnKey = "createdAt" | "createdBy" | "updatedAt" | "updatedBy";
11
- type AuditKeys<TTable extends Table> = { [K in keyof TableColumns<TTable>]: K extends AuditColumnKey ? TableColumns<TTable>[K]["_"]["hasDefault"] extends true ? K : never : never; }[keyof TableColumns<TTable>];
12
- type InsertColumns<TTable extends Table> = Pick<TableColumns<TTable>, Exclude<keyof InferInsertModel<TTable>, AuditKeys<TTable>>>;
10
+ declare const COLUMN_RULES_KEY = "~zeno";
11
+ /**
12
+ * What a column marker from `@zeno-lib/db/schema` records. `writer` says who
13
+ * writes the column when a client doesn't: `"server"` code (`serverOwned`), or
14
+ * only the `"database"`, through a default and a trigger (the audit columns
15
+ * `timestamps()` and `authorship()` emit). `refine` runs on it in insert and
16
+ * update, and `hint` goes to the form field that edits it.
17
+ */
18
+ type ColumnRule = {
19
+ readonly writer?: "database" | "server";
20
+ readonly refine?: unknown;
21
+ readonly hint?: FieldHint;
22
+ };
23
+ type Writer = NonNullable<ColumnRule["writer"]>;
24
+ /**
25
+ * A table built by `table()`, `unsecureTable()` or `schema(...).table` from
26
+ * `@zeno-lib/db/schema`, which records its column rules. A plain drizzle
27
+ * table has none, so its markers would vanish without a trace.
28
+ */
29
+ type ZenoTable = Table & {
30
+ readonly [COLUMN_RULES_KEY]: {
31
+ readonly columns: object;
32
+ };
33
+ };
34
+ type TableColumnRules<TTable extends ZenoTable> = TTable[typeof COLUMN_RULES_KEY]["columns"];
35
+ type WrittenByKeys<TTable extends ZenoTable, TWriter extends Writer> = { [K in keyof TableColumnRules<TTable>]: TableColumnRules<TTable>[K] extends {
36
+ readonly writer: TWriter;
37
+ } ? K : never; }[keyof TableColumnRules<TTable>];
38
+ type ColumnRefinements<TTable extends ZenoTable> = { [K in keyof TableColumnRules<TTable> as TableColumnRules<TTable>[K] extends {
39
+ readonly refine: unknown;
40
+ } ? K : never]: TableColumnRules<TTable>[K] extends {
41
+ readonly refine: infer TRefine;
42
+ } ? TRefine : never; };
43
+ type IdentityKeys<TTable extends Table> = { [K in keyof TableColumns<TTable>]: TableColumns<TTable>[K]["_"]["identity"] extends "always" | "byDefault" ? K : never; }[keyof TableColumns<TTable>];
44
+ type ClientExcludes = Writer;
45
+ type ServerExcludes = "database";
46
+ type UnwritableKeys<TTable extends ZenoTable, TExcludes extends Writer = ClientExcludes> = IdentityKeys<TTable> | WrittenByKeys<TTable, TExcludes>;
47
+ type InsertColumns<TTable extends ZenoTable, TExcludes extends Writer = ClientExcludes> = Pick<TableColumns<TTable>, Exclude<keyof InferInsertModel<TTable>, UnwritableKeys<TTable, TExcludes>>>;
13
48
  type SelectRefine<TTable extends Table, TCoerce extends CoerceOptions> = BuildRefine<TableColumns<TTable>, TCoerce>;
14
- type InsertRefine<TTable extends Table, TCoerce extends CoerceOptions> = BuildRefine<InsertColumns<TTable>, TCoerce>;
15
- type UpdateRefine<TTable extends Table, TCoerce extends CoerceOptions> = BuildRefine<InsertColumns<TTable>, TCoerce>;
16
- type DefineTableSchemaOptions<TTable extends Table, TCoerce extends CoerceOptions = undefined> = {
49
+ type WriteRefineOptions<TTable extends ZenoTable, TCoerce extends CoerceOptions, TExcludes extends Writer> = BuildRefine<InsertColumns<TTable, TExcludes>, TCoerce>;
50
+ type DefineTableSchemaOptions<TTable extends ZenoTable, TCoerce extends CoerceOptions = undefined, TExcludes extends Writer = ClientExcludes> = {
17
51
  factory?: CreateSchemaFactoryOptions<TCoerce>;
18
- insert?: NoUnknownKeys<InsertRefine<TTable, TCoerce>, InferInsertModel<TTable>>;
52
+ insert?: NoUnknownKeys<WriteRefineOptions<TTable, TCoerce, TExcludes>, InferInsertModel<TTable>>;
19
53
  select?: NoUnknownKeys<SelectRefine<TTable, TCoerce>, InferSelectModel<TTable>>;
20
- update?: UpdateRefine<TTable, TCoerce>;
54
+ update?: WriteRefineOptions<TTable, TCoerce, TExcludes>;
21
55
  };
22
56
  type RefineFromOptions<TOptions, TKey extends string> = TKey extends keyof TOptions ? NonNullable<TOptions[TKey]> extends Record<string, unknown> ? NonNullable<TOptions[TKey]> : undefined : undefined;
57
+ type WriteRefine<TTable extends ZenoTable, TOptions, TKey extends string> = RefineFromOptions<TOptions, TKey> extends (infer TCaller) ? TCaller extends Record<string, unknown> ? Omit<ColumnRefinements<TTable>, keyof TCaller> & TCaller : ColumnRefinements<TTable> : never;
23
58
  type OmitKeys<TSchema, TKeys extends PropertyKey> = TSchema extends z.ZodObject<infer TShape, infer TConfig> ? z.ZodObject<{ [K in keyof TShape as K extends TKeys ? never : K]: TShape[K]; }, TConfig> : never;
24
59
  type IsInsertOptional<TColumn> = TColumn extends Column ? TColumn["_"]["notNull"] extends true ? TColumn["_"]["hasDefault"] extends true ? true : false : true : false;
25
60
  type OptionalRefinedInsert<TSchema, TTable extends Table, TRefine> = TSchema extends z.ZodObject<infer TShape, infer TConfig> ? z.ZodObject<{ [K in keyof TShape]: K extends keyof TRefine ? TRefine[K] extends ((schema: never) => unknown) ? (K extends keyof TableColumns<TTable> ? IsInsertOptional<TableColumns<TTable>[K]> : false) extends true ? TShape[K] extends z.ZodOptional ? TShape[K] : z.ZodOptional<TShape[K]> : TShape[K] : TShape[K] : TShape[K]; }, TConfig> : never;
26
- type DefineTableSchemaResult<TTable extends Table, TCoerce extends CoerceOptions, TOptions> = {
27
- insert: OmitKeys<OptionalRefinedInsert<BuildSchema<"insert", TableColumns<TTable>, RefineFromOptions<TOptions, "insert">, TCoerce>, TTable, RefineFromOptions<TOptions, "insert">>, AuditKeys<TTable>>;
61
+ type DefineTableSchemaResult<TTable extends ZenoTable, TCoerce extends CoerceOptions, TOptions, TExcludes extends Writer = ClientExcludes> = {
62
+ /** The table the schemas come from, for `ownedRows`. */
63
+ table: TTable;
64
+ insert: OmitKeys<OptionalRefinedInsert<BuildSchema<"insert", TableColumns<TTable>, WriteRefine<TTable, TOptions, "insert">, TCoerce>, TTable, WriteRefine<TTable, TOptions, "insert">>, UnwritableKeys<TTable, TExcludes>>;
28
65
  select: BuildSchema<"select", TableColumns<TTable>, RefineFromOptions<TOptions, "select">, TCoerce>;
29
- update: OmitKeys<BuildSchema<"update", TableColumns<TTable>, RefineFromOptions<TOptions, "update">, TCoerce>, AuditKeys<TTable>>;
66
+ update: OmitKeys<BuildSchema<"update", TableColumns<TTable>, WriteRefine<TTable, TOptions, "update">, TCoerce>, UnwritableKeys<TTable, TExcludes>>;
67
+ };
68
+ /** What a column's `withRule` hints the form field that edits it. */
69
+ type FieldHint = {
70
+ readonly field: string;
71
+ };
72
+ declare function defineTableSchema<TTable extends ZenoTable>(table: TTable): DefineTableSchemaResult<TTable, undefined, NoTableSchemaOptions>;
73
+ declare function defineTableSchema<TTable extends ZenoTable, TCoerce extends CoerceOptions = undefined, TOptions extends DefineTableSchemaOptions<TTable, TCoerce> = DefineTableSchemaOptions<TTable, TCoerce>>(table: TTable, options: TOptions): DefineTableSchemaResult<TTable, TCoerce, TOptions>;
74
+ /**
75
+ * `defineTableSchema` for code that runs only on the server, such as a sync
76
+ * job, which writes the columns `serverOwned()` keeps from clients. Identity
77
+ * and audit columns stay out, as the database writes them. Never parse what a
78
+ * client sends with it: a server action's input is a client's.
79
+ */
80
+ declare function defineServerTableSchema<TTable extends ZenoTable>(table: TTable): DefineTableSchemaResult<TTable, undefined, NoTableSchemaOptions, ServerExcludes>;
81
+ declare function defineServerTableSchema<TTable extends ZenoTable, TCoerce extends CoerceOptions = undefined, TOptions extends DefineTableSchemaOptions<TTable, TCoerce, ServerExcludes> = DefineTableSchemaOptions<TTable, TCoerce, ServerExcludes>>(table: TTable, options: TOptions): DefineTableSchemaResult<TTable, TCoerce, TOptions, ServerExcludes>;
82
+ type ColumnKey<TTable extends Table> = Extract<keyof TableColumns<TTable>, string>;
83
+ type OwnableSchema = {
84
+ insert: z.ZodObject;
85
+ select: {
86
+ shape: {
87
+ id: z.ZodType;
88
+ };
89
+ };
90
+ table: ZenoTable;
91
+ };
92
+ type OwnedRowSchema<TSchema extends OwnableSchema, TParentKey extends string> = TSchema["insert"] extends z.ZodObject<infer TShape, infer TConfig> ? z.ZodObject<{ [K in keyof TShape as K extends TParentKey ? never : K]: TShape[K]; } & {
93
+ id: z.ZodOptional<TSchema["select"]["shape"]["id"]>;
94
+ }, TConfig> : never;
95
+ /**
96
+ * A table's rows that belong to one parent row and are edited as a list, such
97
+ * as an order's lines. `syncRows` from `@zeno-lib/db/query` takes it in place
98
+ * of the table, and a form validates each row with `row`.
99
+ */
100
+ type OwnedRows<TTable extends ZenoTable, TParentKey extends string, TRow extends z.ZodObject> = {
101
+ /** The columns that point at the parent row, which `row` leaves out. */
102
+ readonly parent: readonly TParentKey[];
103
+ /**
104
+ * One row as a client sends it: the `insert` columns but the parent's, and
105
+ * the `id` of a stored row, absent on a new one.
106
+ */
107
+ readonly row: TRow;
108
+ readonly table: TTable;
30
109
  };
31
- declare function defineTableSchema<TTable extends Table>(table: TTable): DefineTableSchemaResult<TTable, undefined, NoTableSchemaOptions>;
32
- declare function defineTableSchema<TTable extends Table, TCoerce extends CoerceOptions = undefined, TOptions extends DefineTableSchemaOptions<TTable, TCoerce> = DefineTableSchemaOptions<TTable, TCoerce>>(table: TTable, options: TOptions): DefineTableSchemaResult<TTable, TCoerce, TOptions>;
110
+ /**
111
+ * The rows of `schema`'s table that belong to a parent through the `parent`
112
+ * columns, for a form that edits them as a list and the `syncRows` that saves
113
+ * it:
114
+ *
115
+ * ```ts
116
+ * const orderLines = ownedRows(defineTableSchema(lines), { parent: "orderId" })
117
+ *
118
+ * const orderSchema = defineTableSchema(orders).insert.extend({
119
+ * lines: z.array(orderLines.row),
120
+ * })
121
+ *
122
+ * await syncRows(tx, orderLines, { parent: { orderId }, rows: input.lines })
123
+ * ```
124
+ *
125
+ * The table needs a sequential `id`. A link table matched by a natural key
126
+ * goes to `syncRows` with its `key` option instead.
127
+ */
128
+ declare const ownedRows: <TSchema extends OwnableSchema, TParentKey extends ColumnKey<TSchema["table"]>>(schema: TSchema, { parent }: {
129
+ parent: TParentKey | readonly TParentKey[];
130
+ }) => OwnedRows<TSchema["table"], TParentKey, OwnedRowSchema<TSchema, TParentKey>>;
131
+ /**
132
+ * The columns a client may write, for every Zeno table among `exports` (a
133
+ * schema barrel, say), keyed by name (`schema.table` outside `public`): what `defineTableSchema`'s
134
+ * `insert` accepts, and `update` accepts the same. Columns are writable unless
135
+ * marked, so snapshot this in one test, and a newly writable column shows up
136
+ * as a diff in review.
137
+ */
138
+ declare const writableColumns: (exports: Record<string, unknown>) => Record<string, string[]>;
33
139
  //#endregion
34
- export { type DefineTableSchemaOptions, type DefineTableSchemaResult, createInsertSchema, createSchemaFactory, createSelectSchema, createUpdateSchema, defineTableSchema };
140
+ export { type DefineTableSchemaOptions, type DefineTableSchemaResult, type OwnedRows, type ZenoTable, createInsertSchema, createSchemaFactory, createSelectSchema, createUpdateSchema, defineServerTableSchema, defineTableSchema, ownedRows, writableColumns };
package/dist/index.mjs CHANGED
@@ -1,18 +1,52 @@
1
- import { getColumns } from "drizzle-orm";
1
+ import { Table, getColumns, getTableName, getTableUniqueName, is } from "drizzle-orm";
2
2
  import { createInsertSchema, createSchemaFactory, createSchemaFactory as createSchemaFactory$1, createSelectSchema, createUpdateSchema } from "drizzle-orm/zod";
3
+ import { z } from "zod";
3
4
  //#region src/index.ts
4
- const AUDIT_COLUMN_KEYS = /* @__PURE__ */ new Set([
5
- "createdAt",
6
- "createdBy",
7
- "updatedAt",
8
- "updatedBy"
9
- ]);
10
- const auditKeys = (table) => {
5
+ const COLUMN_RULES_KEY = "~zeno";
6
+ const columnRules = (table) => table[COLUMN_RULES_KEY].columns;
7
+ const CLIENT_EXCLUDES = /* @__PURE__ */ new Set(["database", "server"]);
8
+ const SERVER_EXCLUDES = /* @__PURE__ */ new Set(["database"]);
9
+ const unwritableKeys = (table, excludes) => {
11
10
  const columns = getColumns(table);
11
+ const rules = columnRules(table);
12
12
  const keys = [];
13
- for (const key of Object.keys(columns)) if (AUDIT_COLUMN_KEYS.has(key) && columns[key]?.hasDefault) keys.push(key);
13
+ for (const key of Object.keys(columns)) {
14
+ const isIdentity = columns[key]?.generatedIdentity !== void 0;
15
+ const writer = rules[key]?.writer;
16
+ if (isIdentity || writer !== void 0 && excludes.has(writer)) keys.push(key);
17
+ }
14
18
  return keys;
15
19
  };
20
+ const composeRefinements = (refinements, generated) => {
21
+ const merged = { ...refinements };
22
+ for (const key of Object.keys(generated)) {
23
+ const inner = generated[key];
24
+ const refinement = refinements?.[key];
25
+ if (refinement === void 0) merged[key] = inner;
26
+ else if (typeof refinement === "function") merged[key] = (schema) => refinement(inner(schema));
27
+ }
28
+ return merged;
29
+ };
30
+ const FIELD_HINTS_KEY = Symbol.for("@zeno-lib/field-hints");
31
+ const fieldHints = (() => {
32
+ const holder = globalThis;
33
+ holder[FIELD_HINTS_KEY] ??= z.registry();
34
+ return holder[FIELD_HINTS_KEY];
35
+ })();
36
+ const withHint = (rule, hint) => (schema) => {
37
+ const ruled = rule(schema);
38
+ fieldHints.add(ruled, hint);
39
+ return ruled;
40
+ };
41
+ const withRuleRefinements = (table, refinements) => {
42
+ const rules = columnRules(table);
43
+ const generated = {};
44
+ for (const key of Object.keys(rules)) {
45
+ const { hint, refine } = rules[key] ?? {};
46
+ if (typeof refine === "function") generated[key] = hint === void 0 ? refine : withHint(refine, hint);
47
+ }
48
+ return composeRefinements(refinements, generated);
49
+ };
16
50
  const isPositiveIdentity = (column) => {
17
51
  const identity = column.generatedIdentity;
18
52
  if (identity === void 0) return false;
@@ -20,33 +54,108 @@ const isPositiveIdentity = (column) => {
20
54
  return Number(increment) > 0 && Number(minValue) > 0 && Number(startWith) > 0;
21
55
  };
22
56
  const toPositive = (schema) => typeof schema.positive === "function" ? schema.positive() : schema;
23
- const withPositiveIdentities = (table, refinements = {}) => {
57
+ const positiveCheck = (column) => isPositiveIdentity(column) ? toPositive : void 0;
58
+ const TIMESTAMP = /^(\d{4}-\d{2}-\d{2})[T ](?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?([+-](?:0\d|1[0-5])(?::?[0-5]\d)?|Z)?$/;
59
+ const INFINITIES = /* @__PURE__ */ new Set(["-infinity", "infinity"]);
60
+ const ISO_DATE = z.iso.date();
61
+ const isDate = (value) => ISO_DATE.safeParse(value).success;
62
+ const isTimestamp = (withTimezone) => (value) => {
63
+ const [, day, offset] = TIMESTAMP.exec(value) ?? [];
64
+ return day !== void 0 && isDate(day) && offset !== void 0 === withTimezone;
65
+ };
66
+ const withFormat = (format, isValid) => (schema) => schema.check((context) => {
67
+ if (!(INFINITIES.has(context.value) || isValid(context.value))) context.issues.push({
68
+ code: "invalid_format",
69
+ continue: true,
70
+ format,
71
+ input: context.value
72
+ });
73
+ });
74
+ const formatCheck = (column) => {
75
+ const { dimensions = 0, withTimezone = false } = column;
76
+ if (dimensions > 0) return;
77
+ switch (column.dataType) {
78
+ case "string date": return withFormat("date", isDate);
79
+ case "string timestamp": return withFormat("datetime", isTimestamp(withTimezone));
80
+ default: return;
81
+ }
82
+ };
83
+ const withChecks = (table, checkFor, refinements) => {
24
84
  const columns = getColumns(table);
25
- const merged = { ...refinements };
85
+ const generated = {};
26
86
  for (const key of Object.keys(columns)) {
27
87
  const column = columns[key];
28
- if (column === void 0 || !isPositiveIdentity(column)) continue;
29
- const refinement = refinements[key];
30
- if (refinement === void 0) merged[key] = toPositive;
31
- else if (typeof refinement === "function") merged[key] = (schema) => refinement(toPositive(schema));
88
+ const check = column === void 0 ? void 0 : checkFor(column);
89
+ if (check !== void 0) generated[key] = check;
32
90
  }
33
- return merged;
91
+ return composeRefinements(refinements, generated);
34
92
  };
35
- function defineTableSchema(table, options) {
93
+ const omitPresent = (schema, keys) => {
94
+ const mask = {};
95
+ for (const key of keys) if (key in schema.shape) mask[key] = true;
96
+ return schema.omit(mask);
97
+ };
98
+ const buildTableSchema = (name, table, options, excludes) => {
99
+ if (!(COLUMN_RULES_KEY in table)) throw new TypeError(`${name} reads the column rules of a table from table(), unsecureTable() or schema(...).table in @zeno-lib/db/schema, and "${getTableName(table)}" has none.`);
36
100
  const schemaFactory = createSchemaFactory$1(options?.factory);
37
- const audit = auditKeys(table);
38
- const omitAudit = (schema) => {
39
- const mask = {};
40
- for (const key of audit) if (key in schema.shape) mask[key] = true;
41
- return schema.omit(mask);
42
- };
101
+ const unwritable = unwritableKeys(table, excludes);
102
+ const omitUnwritable = (schema) => omitPresent(schema, unwritable);
43
103
  const factory = schemaFactory;
44
- const build = (variant, create) => create(table, withPositiveIdentities(table, options?.[variant]));
104
+ const write = (variant, create) => omitUnwritable(create(table, withChecks(table, formatCheck, withRuleRefinements(table, options?.[variant]))));
45
105
  return {
46
- insert: omitAudit(build("insert", factory.createInsertSchema)),
47
- select: build("select", factory.createSelectSchema),
48
- update: omitAudit(build("update", factory.createUpdateSchema))
106
+ insert: write("insert", factory.createInsertSchema),
107
+ select: factory.createSelectSchema(table, withChecks(table, positiveCheck, options?.select)),
108
+ table,
109
+ update: write("update", factory.createUpdateSchema)
49
110
  };
111
+ };
112
+ function defineTableSchema(table, options) {
113
+ return buildTableSchema("defineTableSchema", table, options, CLIENT_EXCLUDES);
114
+ }
115
+ function defineServerTableSchema(table, options) {
116
+ return buildTableSchema("defineServerTableSchema", table, options, SERVER_EXCLUDES);
50
117
  }
118
+ /**
119
+ * The rows of `schema`'s table that belong to a parent through the `parent`
120
+ * columns, for a form that edits them as a list and the `syncRows` that saves
121
+ * it:
122
+ *
123
+ * ```ts
124
+ * const orderLines = ownedRows(defineTableSchema(lines), { parent: "orderId" })
125
+ *
126
+ * const orderSchema = defineTableSchema(orders).insert.extend({
127
+ * lines: z.array(orderLines.row),
128
+ * })
129
+ *
130
+ * await syncRows(tx, orderLines, { parent: { orderId }, rows: input.lines })
131
+ * ```
132
+ *
133
+ * The table needs a sequential `id`. A link table matched by a natural key
134
+ * goes to `syncRows` with its `key` option instead.
135
+ */
136
+ const ownedRows = (schema, { parent }) => {
137
+ const parentKeys = typeof parent === "string" ? [parent] : [...parent];
138
+ const id = schema.select.shape.id;
139
+ if (id === void 0) throw new TypeError(`ownedRows tells rows apart by their id, and "${getTableName(schema.table)}" has none.`);
140
+ return {
141
+ parent: parentKeys,
142
+ row: omitPresent(schema.insert, parentKeys).extend({ id: id.optional() }),
143
+ table: schema.table
144
+ };
145
+ };
146
+ const isZenoTable = (value) => is(value, Table) && COLUMN_RULES_KEY in value;
147
+ /**
148
+ * The columns a client may write, for every Zeno table among `exports` (a
149
+ * schema barrel, say), keyed by name (`schema.table` outside `public`): what `defineTableSchema`'s
150
+ * `insert` accepts, and `update` accepts the same. Columns are writable unless
151
+ * marked, so snapshot this in one test, and a newly writable column shows up
152
+ * as a diff in review.
153
+ */
154
+ const writableColumns = (exports) => {
155
+ const tables = Object.keys(exports).map((key) => exports[key]).filter(isZenoTable).map((table) => [getTableUniqueName(table), table]).sort(([a], [b]) => a < b ? -1 : 1);
156
+ const columns = {};
157
+ for (const [name, table] of tables) columns[name] = Object.keys(buildTableSchema("writableColumns", table, void 0, CLIENT_EXCLUDES).insert.shape).sort();
158
+ return columns;
159
+ };
51
160
  //#endregion
52
- export { createInsertSchema, createSchemaFactory, createSelectSchema, createUpdateSchema, defineTableSchema };
161
+ export { createInsertSchema, createSchemaFactory, createSelectSchema, createUpdateSchema, defineServerTableSchema, defineTableSchema, ownedRows, writableColumns };
package/package.json CHANGED
@@ -1,6 +1,10 @@
1
1
  {
2
2
  "name": "@zeno-lib/schema",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
+ "repository": {
5
+ "url": "https://github.com/zeno-lib/zeno.git",
6
+ "directory": "packages/schema"
7
+ },
4
8
  "type": "module",
5
9
  "exports": {
6
10
  ".": {
@@ -13,24 +17,20 @@
13
17
  "dist",
14
18
  "src"
15
19
  ],
16
- "publishConfig": {
17
- "access": "public"
18
- },
19
- "repository": {
20
- "url": "https://github.com/zeno-lib/zeno.git",
21
- "directory": "packages/schema"
20
+ "devDependencies": {
21
+ "@zeno-lib/typescript": "^1.2.0",
22
+ "drizzle-orm": "1.0.0-rc.5-5935859",
23
+ "tsdown": "^0.23.0",
24
+ "typescript": "7.0.2",
25
+ "vitest": "5.0.3",
26
+ "zod": "^4.6.5"
22
27
  },
23
28
  "peerDependencies": {
24
- "drizzle-orm": "1.0.0-rc.3",
29
+ "drizzle-orm": "1.0.0-rc.5-5935859",
25
30
  "zod": ">=4"
26
31
  },
27
- "devDependencies": {
28
- "drizzle-orm": "1.0.0-rc.3",
29
- "tsdown": "^0.22.14",
30
- "typescript": "7.0.2",
31
- "vitest": "4.1.11",
32
- "zod": "^4.4.3",
33
- "@zeno-lib/typescript": "^1.1.0"
32
+ "publishConfig": {
33
+ "access": "public"
34
34
  },
35
35
  "scripts": {
36
36
  "build": "tsdown",
@@ -1,9 +1,9 @@
1
- import { sql } from "drizzle-orm"
2
- import { integer, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
1
+ import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"
3
2
  import { expectTypeOf, test } from "vitest"
4
3
  import { z } from "zod"
5
4
 
6
5
  import { defineTableSchema } from "./index"
6
+ import { zenoTable } from "./test-table"
7
7
 
8
8
  type StandardSchema<T> = {
9
9
  readonly "~standard": {
@@ -12,17 +12,17 @@ type StandardSchema<T> = {
12
12
  }
13
13
  }
14
14
 
15
- function acceptsFormsSchema<TFormData>(
15
+ declare function acceptsFormsSchema<TFormData>(
16
16
  _schema: StandardSchema<TFormData>
17
- ): void {
18
- return
19
- }
17
+ ): void
20
18
 
21
- const posts = pgTable("posts", {
22
- id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
23
- slug: text("slug").notNull(),
24
- title: text("title").notNull(),
25
- })
19
+ const posts = zenoTable(
20
+ pgTable("posts", {
21
+ id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
22
+ slug: text("slug").notNull(),
23
+ title: text("title").notNull(),
24
+ })
25
+ )
26
26
 
27
27
  test("insert and update schemas infer form-compatible Standard Schema data", () => {
28
28
  const schemas = defineTableSchema(posts)
@@ -54,39 +54,29 @@ test("refined schemas preserve refinement-specific output types", () => {
54
54
  }>()
55
55
  })
56
56
 
57
- test("defaulted audit columns leave the insert and update types", () => {
58
- const notes = pgTable("notes", {
59
- body: text("body").notNull(),
60
- createdAt: timestamp("created_at").notNull().defaultNow(),
61
- createdBy: uuid("created_by").notNull().default(sql`auth.uid()`),
62
- updatedAt: timestamp("updated_at").notNull(),
63
- })
64
- const schemas = defineTableSchema(notes)
57
+ test("a column named like an audit column stays in the insert type", () => {
58
+ const events = zenoTable(
59
+ pgTable("events", {
60
+ createdAt: timestamp("created_at").notNull().defaultNow(),
61
+ label: text("label").notNull(),
62
+ })
63
+ )
64
+ const schemas = defineTableSchema(events)
65
65
 
66
66
  expectTypeOf<z.infer<typeof schemas.insert>>().toEqualTypeOf<{
67
- body: string
68
- updatedAt: Date
67
+ createdAt?: Date | undefined
68
+ label: string
69
69
  }>()
70
- expectTypeOf<z.infer<typeof schemas.update>>().toEqualTypeOf<{
71
- body?: string | undefined
72
- updatedAt?: Date | undefined
73
- }>()
74
- expectTypeOf<keyof z.infer<typeof schemas.select>>().toEqualTypeOf<
75
- "body" | "createdAt" | "createdBy" | "updatedAt"
76
- >()
77
-
78
- defineTableSchema(notes, {
79
- // @ts-expect-error: an omitted audit column has nothing to refine.
80
- insert: { createdBy: (schema) => schema },
81
- })
82
70
  })
83
71
 
84
72
  test("a function refinement keeps a nullable or defaulted insert column optional", () => {
85
- const drafts = pgTable("drafts", {
86
- note: text("note"),
87
- status: text("status").notNull().default("open"),
88
- title: text("title").notNull(),
89
- })
73
+ const drafts = zenoTable(
74
+ pgTable("drafts", {
75
+ note: text("note"),
76
+ status: text("status").notNull().default("open"),
77
+ title: text("title").notNull(),
78
+ })
79
+ )
90
80
  const schemas = defineTableSchema(drafts, {
91
81
  insert: {
92
82
  note: (schema) => schema.min(1),