@classytic/repo-core 0.3.0 → 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.
Files changed (73) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/dist/adapter/index.d.mts +3 -0
  3. package/dist/adapter/index.mjs +2 -0
  4. package/dist/adapter/types.d.mts +222 -0
  5. package/dist/adapter/widen.d.mts +22 -0
  6. package/dist/adapter/widen.mjs +26 -0
  7. package/dist/aggregate/index.d.mts +3 -0
  8. package/dist/aggregate/index.mjs +3 -0
  9. package/dist/aggregate/keyset.d.mts +57 -0
  10. package/dist/aggregate/keyset.mjs +45 -0
  11. package/dist/aggregate/normalize.d.mts +24 -0
  12. package/dist/aggregate/normalize.mjs +28 -0
  13. package/dist/better-auth/index.d.mts +110 -0
  14. package/dist/better-auth/index.mjs +71 -0
  15. package/dist/cache/engine.d.mts +127 -0
  16. package/dist/cache/engine.mjs +235 -0
  17. package/dist/cache/envelope.mjs +32 -0
  18. package/dist/cache/index.d.mts +7 -2
  19. package/dist/cache/index.mjs +6 -2
  20. package/dist/cache/keys.mjs +131 -0
  21. package/dist/cache/memory-adapter.mjs +41 -7
  22. package/dist/cache/options.d.mts +112 -0
  23. package/dist/cache/options.mjs +25 -0
  24. package/dist/cache/plugin/context.d.mts +18 -0
  25. package/dist/cache/plugin/context.mjs +121 -0
  26. package/dist/cache/plugin/index.d.mts +86 -0
  27. package/dist/cache/plugin/index.mjs +78 -0
  28. package/dist/cache/plugin/invalidation-hooks.mjs +35 -0
  29. package/dist/cache/plugin/read-hooks.mjs +96 -0
  30. package/dist/cache/plugin/swr.mjs +20 -0
  31. package/dist/cache/runtime.d.mts +43 -0
  32. package/dist/cache/runtime.mjs +14 -0
  33. package/dist/cache/tag-index.mjs +84 -0
  34. package/dist/cache/timeout-adapter.d.mts +30 -0
  35. package/dist/cache/timeout-adapter.mjs +58 -0
  36. package/dist/cache/types.d.mts +45 -0
  37. package/dist/cache/version-store.mjs +57 -0
  38. package/dist/errors/index.d.mts +2 -1
  39. package/dist/errors/index.mjs +2 -1
  40. package/dist/errors/schema.d.mts +101 -0
  41. package/dist/errors/schema.mjs +78 -0
  42. package/dist/filter/match.mjs +38 -2
  43. package/dist/pagination/canonical.d.mts +8 -8
  44. package/dist/pagination/canonical.mjs +3 -9
  45. package/dist/pagination/cursor.mjs +4 -1
  46. package/dist/pagination/index.d.mts +2 -2
  47. package/dist/pagination/types.d.mts +17 -27
  48. package/dist/plugins/index.d.mts +2 -0
  49. package/dist/plugins/index.mjs +2 -0
  50. package/dist/plugins/tenant-helpers.d.mts +63 -0
  51. package/dist/plugins/tenant-helpers.mjs +84 -0
  52. package/dist/query-parser/index.d.mts +2 -1
  53. package/dist/query-parser/index.mjs +2 -1
  54. package/dist/query-parser/parse-url.mjs +13 -11
  55. package/dist/query-parser/reserved.d.mts +43 -0
  56. package/dist/query-parser/reserved.mjs +56 -0
  57. package/dist/repository/agg-output.d.mts +63 -0
  58. package/dist/repository/agg-output.mjs +89 -0
  59. package/dist/repository/index.d.mts +4 -2
  60. package/dist/repository/index.mjs +3 -1
  61. package/dist/repository/options.d.mts +62 -0
  62. package/dist/repository/options.mjs +57 -0
  63. package/dist/repository/types.d.mts +935 -48
  64. package/dist/schema/field-rules.d.mts +41 -1
  65. package/dist/schema/field-rules.mjs +92 -1
  66. package/dist/schema/index.d.mts +2 -2
  67. package/dist/schema/index.mjs +2 -2
  68. package/dist/schema/types.d.mts +21 -0
  69. package/dist/testing/conformance.mjs +666 -17
  70. package/dist/testing/index.d.mts +2 -2
  71. package/dist/testing/types.d.mts +99 -2
  72. package/package.json +19 -1
  73. package/dist/cache/stable-stringify.d.mts +0 -15
@@ -69,5 +69,45 @@ declare function isFieldUpdateAllowed(fieldName: string, options?: SchemaBuilder
69
69
  * walking the rules themselves.
70
70
  */
71
71
  declare function validateUpdateBody(body?: Record<string, unknown>, options?: SchemaBuilderOptions): ValidationResult;
72
+ /**
73
+ * Merge constraint-style `fieldRules` into a generated schema bag in place.
74
+ *
75
+ * Operates on the three slots that carry property maps — `createBody`,
76
+ * `updateBody`, `response`. `listQuery` and `params` are skipped (their
77
+ * constraint vocabulary is owned by the kit's query parser).
78
+ *
79
+ * Existing constraints on a property always win — the merge only fills in
80
+ * gaps. Kits that already walk `fieldRules` during base-schema assembly
81
+ * can call this helper for free (the checks are no-ops when constraints
82
+ * already exist).
83
+ *
84
+ * Schema slot type is intentionally loose (`Record<string, unknown> | null
85
+ * | undefined`) so adapters can pass either an `OpenApiSchemas`-shaped bag
86
+ * or a kit-native bag without coercion.
87
+ */
88
+ declare function mergeFieldRuleConstraints(schemas: Record<string, unknown> | null | undefined, schemaOptions?: SchemaBuilderOptions): void;
89
+ /**
90
+ * Widen a JSON Schema property to also accept `null`.
91
+ *
92
+ * Handles the three ways a property can be typed:
93
+ * - `type: 'string'` → `type: ['string', 'null']`
94
+ * - `type: [...]` → append `'null'` if missing
95
+ * - `anyOf: [...]` → append `{ type: 'null' }` branch if missing
96
+ *
97
+ * **Enum interaction:** when the widened prop also carries `enum: [...]`,
98
+ * `null` is appended to the enum list too. AJV's `enum` keyword rejects
99
+ * values not in the list regardless of the widened `type`, so
100
+ * `{ type: ['string','null'], enum: ['a','b'] }` alone would still reject
101
+ * `null`. The fix is `enum: ['a','b', null]`. (The `anyOf` branch dodges
102
+ * this entirely — each branch scopes its own enum.)
103
+ *
104
+ * No-op when the schema already admits null (don't double-wrap) or has
105
+ * no `type` / `anyOf` anchor to widen (e.g. Mixed — already accepts null).
106
+ *
107
+ * Mutates in place — callers already treat the slot schema as owned.
108
+ * Exported so adapters that walk `fieldRules` inline can reuse the same
109
+ * widening logic.
110
+ */
111
+ declare function applyNullable(prop: Record<string, unknown>): void;
72
112
  //#endregion
73
- export { applyFieldRules, collectFieldsToOmit, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, validateUpdateBody };
113
+ export { applyFieldRules, applyNullable, collectFieldsToOmit, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, mergeFieldRuleConstraints, validateUpdateBody };
@@ -126,5 +126,96 @@ function validateUpdateBody(body = {}, options = {}) {
126
126
  violations
127
127
  };
128
128
  }
129
+ /**
130
+ * Merge constraint-style `fieldRules` into a generated schema bag in place.
131
+ *
132
+ * Operates on the three slots that carry property maps — `createBody`,
133
+ * `updateBody`, `response`. `listQuery` and `params` are skipped (their
134
+ * constraint vocabulary is owned by the kit's query parser).
135
+ *
136
+ * Existing constraints on a property always win — the merge only fills in
137
+ * gaps. Kits that already walk `fieldRules` during base-schema assembly
138
+ * can call this helper for free (the checks are no-ops when constraints
139
+ * already exist).
140
+ *
141
+ * Schema slot type is intentionally loose (`Record<string, unknown> | null
142
+ * | undefined`) so adapters can pass either an `OpenApiSchemas`-shaped bag
143
+ * or a kit-native bag without coercion.
144
+ */
145
+ function mergeFieldRuleConstraints(schemas, schemaOptions) {
146
+ if (!schemas || typeof schemas !== "object") return;
147
+ const rules = schemaOptions?.fieldRules;
148
+ if (!rules || Object.keys(rules).length === 0) return;
149
+ for (const slot of [
150
+ "createBody",
151
+ "updateBody",
152
+ "response"
153
+ ]) {
154
+ const slotSchema = schemas[slot];
155
+ if (!slotSchema || typeof slotSchema !== "object") continue;
156
+ const properties = slotSchema["properties"];
157
+ if (!properties) continue;
158
+ for (const [field, rule] of Object.entries(rules)) {
159
+ const prop = properties[field];
160
+ if (!prop || typeof prop !== "object") continue;
161
+ if (rule.minLength != null && prop["minLength"] == null) prop["minLength"] = rule.minLength;
162
+ if (rule.maxLength != null && prop["maxLength"] == null) prop["maxLength"] = rule.maxLength;
163
+ if (rule.min != null && prop["minimum"] == null) prop["minimum"] = rule.min;
164
+ if (rule.max != null && prop["maximum"] == null) prop["maximum"] = rule.max;
165
+ if (rule.pattern != null && prop["pattern"] == null) prop["pattern"] = rule.pattern;
166
+ if (rule.enum != null && prop["enum"] == null) prop["enum"] = rule.enum;
167
+ if (rule.description != null && prop["description"] == null) prop["description"] = rule.description;
168
+ if (rule.nullable === true) applyNullable(prop);
169
+ }
170
+ }
171
+ }
172
+ /**
173
+ * Widen a JSON Schema property to also accept `null`.
174
+ *
175
+ * Handles the three ways a property can be typed:
176
+ * - `type: 'string'` → `type: ['string', 'null']`
177
+ * - `type: [...]` → append `'null'` if missing
178
+ * - `anyOf: [...]` → append `{ type: 'null' }` branch if missing
179
+ *
180
+ * **Enum interaction:** when the widened prop also carries `enum: [...]`,
181
+ * `null` is appended to the enum list too. AJV's `enum` keyword rejects
182
+ * values not in the list regardless of the widened `type`, so
183
+ * `{ type: ['string','null'], enum: ['a','b'] }` alone would still reject
184
+ * `null`. The fix is `enum: ['a','b', null]`. (The `anyOf` branch dodges
185
+ * this entirely — each branch scopes its own enum.)
186
+ *
187
+ * No-op when the schema already admits null (don't double-wrap) or has
188
+ * no `type` / `anyOf` anchor to widen (e.g. Mixed — already accepts null).
189
+ *
190
+ * Mutates in place — callers already treat the slot schema as owned.
191
+ * Exported so adapters that walk `fieldRules` inline can reuse the same
192
+ * widening logic.
193
+ */
194
+ function applyNullable(prop) {
195
+ if (Array.isArray(prop["anyOf"])) {
196
+ if (!prop["anyOf"].some((b) => b !== null && typeof b === "object" && (b["type"] === "null" || b["const"] === null))) prop["anyOf"].push({ type: "null" });
197
+ return;
198
+ }
199
+ if (Array.isArray(prop["type"])) {
200
+ if (!prop["type"].includes("null")) prop["type"].push("null");
201
+ widenEnumToIncludeNull(prop);
202
+ return;
203
+ }
204
+ if (typeof prop["type"] === "string") {
205
+ prop["type"] = [prop["type"], "null"];
206
+ widenEnumToIncludeNull(prop);
207
+ return;
208
+ }
209
+ }
210
+ /**
211
+ * Append `null` to `enum` when present. Required because AJV's `enum`
212
+ * keyword is independent of `type` — a value must appear in the enum
213
+ * array verbatim even if the widened type says null is allowed.
214
+ */
215
+ function widenEnumToIncludeNull(prop) {
216
+ if (!Array.isArray(prop["enum"])) return;
217
+ if (prop["enum"].includes(null)) return;
218
+ prop["enum"] = [...prop["enum"], null];
219
+ }
129
220
  //#endregion
130
- export { applyFieldRules, collectFieldsToOmit, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, validateUpdateBody };
221
+ export { applyFieldRules, applyNullable, collectFieldsToOmit, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, mergeFieldRuleConstraints, validateUpdateBody };
@@ -1,4 +1,4 @@
1
1
  import { CrudSchemas, FieldRule, FieldRules, JsonSchema, SchemaBuilderOptions, ValidationResult } from "./types.mjs";
2
- import { applyFieldRules, collectFieldsToOmit, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, validateUpdateBody } from "./field-rules.mjs";
2
+ import { applyFieldRules, applyNullable, collectFieldsToOmit, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, mergeFieldRuleConstraints, validateUpdateBody } from "./field-rules.mjs";
3
3
  import { SchemaGenerator, SchemaGeneratorContext, isSchemaGenerator } from "./generator.mjs";
4
- export { type CrudSchemas, type FieldRule, type FieldRules, type JsonSchema, type SchemaBuilderOptions, type SchemaGenerator, type SchemaGeneratorContext, type ValidationResult, applyFieldRules, collectFieldsToOmit, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, isSchemaGenerator, validateUpdateBody };
4
+ export { type CrudSchemas, type FieldRule, type FieldRules, type JsonSchema, type SchemaBuilderOptions, type SchemaGenerator, type SchemaGeneratorContext, type ValidationResult, applyFieldRules, applyNullable, collectFieldsToOmit, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, isSchemaGenerator, mergeFieldRuleConstraints, validateUpdateBody };
@@ -1,3 +1,3 @@
1
- import { applyFieldRules, collectFieldsToOmit, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, validateUpdateBody } from "./field-rules.mjs";
1
+ import { applyFieldRules, applyNullable, collectFieldsToOmit, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, mergeFieldRuleConstraints, validateUpdateBody } from "./field-rules.mjs";
2
2
  import { isSchemaGenerator } from "./generator.mjs";
3
- export { applyFieldRules, collectFieldsToOmit, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, isSchemaGenerator, validateUpdateBody };
3
+ export { applyFieldRules, applyNullable, collectFieldsToOmit, getImmutableFields, getSystemManagedFields, isFieldUpdateAllowed, isSchemaGenerator, mergeFieldRuleConstraints, validateUpdateBody };
@@ -36,6 +36,27 @@ interface FieldRule {
36
36
  * narrow on the same flag.
37
37
  */
38
38
  hidden?: boolean;
39
+ /** String minimum length — JSON Schema `minLength`. */
40
+ minLength?: number;
41
+ /** String maximum length — JSON Schema `maxLength`. */
42
+ maxLength?: number;
43
+ /** Number minimum — JSON Schema `minimum`. */
44
+ min?: number;
45
+ /** Number maximum — JSON Schema `maximum`. */
46
+ max?: number;
47
+ /** Regex pattern — JSON Schema `pattern`. */
48
+ pattern?: string;
49
+ /** Allowed values — JSON Schema `enum`. */
50
+ enum?: ReadonlyArray<string | number>;
51
+ /**
52
+ * Widen the property type to also accept `null`. Implemented as a
53
+ * draft-07 type tuple (`type: ['string','null']`) or anyOf branch
54
+ * (`anyOf: [..., { type: 'null' }]`). Existing nullability is
55
+ * preserved — never double-widens.
56
+ */
57
+ nullable?: boolean;
58
+ /** Human-readable description — JSON Schema `description`. */
59
+ description?: string;
39
60
  }
40
61
  /** Map of field name → FieldRule. */
41
62
  interface FieldRules {