better-call 0.0.0-experimental.01ff4b77

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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +265 -0
  3. package/dist/capability.cjs +417 -0
  4. package/dist/capability.cjs.map +1 -0
  5. package/dist/capability.d.cts +189 -0
  6. package/dist/capability.d.mts +189 -0
  7. package/dist/capability.mjs +401 -0
  8. package/dist/capability.mjs.map +1 -0
  9. package/dist/error.cjs +76 -0
  10. package/dist/error.cjs.map +1 -0
  11. package/dist/error.d.cts +51 -0
  12. package/dist/error.d.mts +51 -0
  13. package/dist/error.mjs +72 -0
  14. package/dist/error.mjs.map +1 -0
  15. package/dist/fn.cjs +350 -0
  16. package/dist/fn.cjs.map +1 -0
  17. package/dist/fn.d.cts +378 -0
  18. package/dist/fn.d.mts +378 -0
  19. package/dist/fn.mjs +350 -0
  20. package/dist/fn.mjs.map +1 -0
  21. package/dist/index.cjs +43 -0
  22. package/dist/index.cjs.map +1 -0
  23. package/dist/index.d.cts +50 -0
  24. package/dist/index.d.mts +50 -0
  25. package/dist/index.mjs +24 -0
  26. package/dist/index.mjs.map +1 -0
  27. package/dist/module.cjs +176 -0
  28. package/dist/module.cjs.map +1 -0
  29. package/dist/module.d.cts +283 -0
  30. package/dist/module.d.mts +283 -0
  31. package/dist/module.mjs +166 -0
  32. package/dist/module.mjs.map +1 -0
  33. package/dist/plugins/db.cjs +24 -0
  34. package/dist/plugins/db.cjs.map +1 -0
  35. package/dist/plugins/db.d.cts +18 -0
  36. package/dist/plugins/db.d.mts +18 -0
  37. package/dist/plugins/db.mjs +19 -0
  38. package/dist/plugins/db.mjs.map +1 -0
  39. package/dist/plugins/http/attrs.cjs +91 -0
  40. package/dist/plugins/http/attrs.cjs.map +1 -0
  41. package/dist/plugins/http/attrs.d.cts +26 -0
  42. package/dist/plugins/http/attrs.d.mts +26 -0
  43. package/dist/plugins/http/attrs.mjs +87 -0
  44. package/dist/plugins/http/attrs.mjs.map +1 -0
  45. package/dist/plugins/http/cookie.cjs +117 -0
  46. package/dist/plugins/http/cookie.cjs.map +1 -0
  47. package/dist/plugins/http/cookie.d.cts +637 -0
  48. package/dist/plugins/http/cookie.d.mts +637 -0
  49. package/dist/plugins/http/cookie.mjs +113 -0
  50. package/dist/plugins/http/cookie.mjs.map +1 -0
  51. package/dist/plugins/http/error.cjs +39 -0
  52. package/dist/plugins/http/error.cjs.map +1 -0
  53. package/dist/plugins/http/error.d.cts +38 -0
  54. package/dist/plugins/http/error.d.mts +38 -0
  55. package/dist/plugins/http/error.mjs +35 -0
  56. package/dist/plugins/http/error.mjs.map +1 -0
  57. package/dist/plugins/http/handle.cjs +73 -0
  58. package/dist/plugins/http/handle.cjs.map +1 -0
  59. package/dist/plugins/http/handle.d.cts +955 -0
  60. package/dist/plugins/http/handle.d.mts +955 -0
  61. package/dist/plugins/http/handle.mjs +72 -0
  62. package/dist/plugins/http/handle.mjs.map +1 -0
  63. package/dist/plugins/http/redirect.cjs +82 -0
  64. package/dist/plugins/http/redirect.cjs.map +1 -0
  65. package/dist/plugins/http/redirect.d.cts +67 -0
  66. package/dist/plugins/http/redirect.d.mts +67 -0
  67. package/dist/plugins/http/redirect.mjs +79 -0
  68. package/dist/plugins/http/redirect.mjs.map +1 -0
  69. package/dist/plugins/http/request.cjs +63 -0
  70. package/dist/plugins/http/request.cjs.map +1 -0
  71. package/dist/plugins/http/request.d.cts +142 -0
  72. package/dist/plugins/http/request.d.mts +142 -0
  73. package/dist/plugins/http/request.mjs +61 -0
  74. package/dist/plugins/http/request.mjs.map +1 -0
  75. package/dist/plugins/http/response.cjs +13 -0
  76. package/dist/plugins/http/response.cjs.map +1 -0
  77. package/dist/plugins/http/response.d.cts +18 -0
  78. package/dist/plugins/http/response.d.mts +18 -0
  79. package/dist/plugins/http/response.mjs +13 -0
  80. package/dist/plugins/http/response.mjs.map +1 -0
  81. package/dist/plugins/http.cjs +64 -0
  82. package/dist/plugins/http.cjs.map +1 -0
  83. package/dist/plugins/http.d.cts +945 -0
  84. package/dist/plugins/http.d.mts +945 -0
  85. package/dist/plugins/http.mjs +37 -0
  86. package/dist/plugins/http.mjs.map +1 -0
  87. package/dist/plugins/read-only.cjs +19 -0
  88. package/dist/plugins/read-only.cjs.map +1 -0
  89. package/dist/plugins/read-only.d.cts +17 -0
  90. package/dist/plugins/read-only.d.mts +17 -0
  91. package/dist/plugins/read-only.mjs +19 -0
  92. package/dist/plugins/read-only.mjs.map +1 -0
  93. package/dist/schema.cjs +245 -0
  94. package/dist/schema.cjs.map +1 -0
  95. package/dist/schema.d.cts +411 -0
  96. package/dist/schema.d.mts +411 -0
  97. package/dist/schema.mjs +235 -0
  98. package/dist/schema.mjs.map +1 -0
  99. package/dist/scope.d.cts +31 -0
  100. package/dist/scope.d.mts +31 -0
  101. package/dist/storage.cjs +308 -0
  102. package/dist/storage.cjs.map +1 -0
  103. package/dist/storage.d.cts +220 -0
  104. package/dist/storage.d.mts +220 -0
  105. package/dist/storage.mjs +302 -0
  106. package/dist/storage.mjs.map +1 -0
  107. package/dist/types.d.cts +8 -0
  108. package/dist/types.d.mts +8 -0
  109. package/dist/var.cjs +186 -0
  110. package/dist/var.cjs.map +1 -0
  111. package/dist/var.d.cts +46 -0
  112. package/dist/var.d.mts +46 -0
  113. package/dist/var.mjs +177 -0
  114. package/dist/var.mjs.map +1 -0
  115. package/package.json +114 -0
@@ -0,0 +1,220 @@
1
+ import { OnEntry } from "./module.cjs";
2
+ import { NameOfVar, ValueOfVar, VarDefination } from "./var.cjs";
3
+ //#region src/storage.d.ts
4
+ /** Per-field operators, for everything equality can't say: expiry sweeps
5
+ * (`lt`), revocation lists (`in`), guarded counters (`lt` as the guard).
6
+ * A bare value stays plain equality - the common case reads like data. */
7
+ type WhereOps<V> = {
8
+ eq?: V;
9
+ ne?: V;
10
+ lt?: V;
11
+ lte?: V;
12
+ gt?: V;
13
+ gte?: V;
14
+ in?: readonly V[];
15
+ notIn?: readonly V[];
16
+ contains?: string;
17
+ startsWith?: string;
18
+ endsWith?: string;
19
+ };
20
+ type WhereOp = keyof WhereOps<unknown>;
21
+ /** AND across fields; each field a bare value (equality) or operators. */
22
+ type Where<R> = { [K in keyof R]?: R[K] | WhereOps<R[K]>; };
23
+ /** One normalized clause: `{ tag: "a" }` -> `{ field: "tag", op: "eq" }`. */
24
+ type Condition = {
25
+ field: string;
26
+ op: WhereOp;
27
+ value: unknown;
28
+ };
29
+ /** Flatten a where to normalized conditions - the adapter author's
30
+ * translation seam: map each condition onto your query language, AND them. */
31
+ declare const conditionsOf: (where?: Record<string, unknown>) => Condition[];
32
+ /** Does a row satisfy a where? The in-memory evaluator - the dummy adapter
33
+ * runs on it, and any adapter over an unqueryable backend can too. */
34
+ declare const matchesWhere: (row: Record<string, unknown>, where?: Record<string, unknown>) => boolean;
35
+ /** Shaping for `findMany`: sort, then window. */
36
+ type FindManyOptions<R> = {
37
+ limit?: number;
38
+ offset?: number;
39
+ sortBy?: {
40
+ field: keyof R & string;
41
+ direction?: "asc" | "desc";
42
+ };
43
+ };
44
+ /** One model's CRUD surface. `create` is generic so a row EXTENDED by a
45
+ * mounted module (an account with a password field) keeps its extra
46
+ * fields through the round-trip outside a widened scope. Inside a scope
47
+ * that mounts `v.extend` / same-name customize on the model var, {@link
48
+ * WidenSchemaFns} rewrites `R` (see `$modelVar`) the same way it widens
49
+ * `v.fn.type({ input: user })`. */
50
+ type Collection<R, N extends string = string> = {
51
+ create: <T extends R>(data: T) => Promise<T>;
52
+ findOne: (where: Where<R>) => Promise<R | null>;
53
+ findMany: (where?: Where<R>, options?: FindManyOptions<R>) => Promise<R[]>;
54
+ /** Merge `patch` into the FIRST match - null when nothing matched. */
55
+ update: (where: Where<R>, patch: Partial<R>) => Promise<R | null>;
56
+ /** Remove every match; how many is the answer. */
57
+ delete: (where: Where<R>) => Promise<number>;
58
+ count: (where?: Where<R>) => Promise<number>;
59
+ /** Claim the FIRST match: the row comes back and is GONE, atomically -
60
+ * two racing consumers never both get it. Single-use credentials
61
+ * (verification tokens, one-time codes) hang off this. */
62
+ consumeOne: (where: Where<R>) => Promise<R | null>;
63
+ /** Add to numeric fields of the FIRST match, atomically; the where is
64
+ * also the GUARD (`count: { lt: max }`), so check-and-bump is one op -
65
+ * null means no row passed it. Rate limiting hangs off this. */
66
+ incrementOne: (where: Where<R>, increments: Partial<Record<keyof R & string, number>>) => Promise<R | null>;
67
+ /**
68
+ * Phantom: the model var's name. Scope resolution reads it to WIDEN
69
+ * `R` with everything the scope mounts on that var - same role as
70
+ * `$fnVar` on fn schemas. Optional, so plain collection objects still
71
+ * satisfy the type.
72
+ */
73
+ readonly $modelVar?: [N];
74
+ };
75
+ /**
76
+ * What a real database implements: six required verbs, models addressed by
77
+ * NAME, `where` as bare-equality fields and/or operator objects
78
+ * (`conditionsOf` normalizes either form). The storage API above never
79
+ * changes - adapters translate it, sync or async.
80
+ *
81
+ * The optional verbs are ATOMICITY upgrades: without `consumeOne` /
82
+ * `incrementOne` the storage falls back to find-then-write - correct alone,
83
+ * racy under contention - and without `transaction` a `$transaction` block
84
+ * runs plainly. Implement them where the backend has the primitive.
85
+ */
86
+ type StorageAdapter = {
87
+ create: (model: string, data: Record<string, unknown>) => unknown;
88
+ findOne: (model: string, where: Record<string, unknown>) => unknown;
89
+ findMany: (model: string, where?: Record<string, unknown>, options?: FindManyOptions<Record<string, unknown>>) => unknown;
90
+ update: (model: string, where: Record<string, unknown>, patch: Record<string, unknown>) => unknown;
91
+ delete: (model: string, where: Record<string, unknown>) => unknown;
92
+ count: (model: string, where?: Record<string, unknown>) => unknown;
93
+ /** Atomic find-and-delete of the first match. */
94
+ consumeOne?: (model: string, where: Record<string, unknown>) => unknown;
95
+ /** Atomic guarded add to numeric fields of the first match. */
96
+ incrementOne?: (model: string, where: Record<string, unknown>, increments: Record<string, number>) => unknown;
97
+ /** Run `run` against a transaction-bound view of this adapter:
98
+ * committed when it resolves, rolled back when it throws. */
99
+ transaction?: <T>(run: (tx: StorageAdapter) => Promise<T>) => Promise<T>;
100
+ };
101
+ /** The DUMMY adapter: rows in arrays, one per model. Implements the whole
102
+ * surface - the optional verbs by mutation (single-threaded, so "atomic"),
103
+ * transactions by snapshot-and-restore. */
104
+ declare const memoryAdapter: () => StorageAdapter;
105
+ type AnyVar = VarDefination<any, any, any, any>;
106
+ /** Persistence facts about one field the ROW SHAPE can't say - consumed by
107
+ * schema generators and adapters, never acted on by the runtime. (Read
108
+ * shaping like redaction is a `$on` hook, not metadata.) Prefer declaring
109
+ * these via `db.unique` / `db.indexed` / `db.references` / `db.id` on the
110
+ * field schema; `ModelConfig.fields` remains an override. */
111
+ type FieldMeta = {
112
+ /** Primary key for this model. */
113
+ id?: boolean;
114
+ /** No two rows share a value. */
115
+ unique?: boolean;
116
+ /** Worth an index. */
117
+ index?: boolean;
118
+ /** Foreign key: this field holds `model.field` values. */
119
+ references?: {
120
+ model: string;
121
+ field: string;
122
+ onDelete?: "cascade" | "set null" | "restrict";
123
+ };
124
+ };
125
+ /** Read `$attrs.db` off each field of a var's object schema. */
126
+ declare const fieldsFromSchema: (sv: AnyVar) => Record<string, FieldMeta>;
127
+ /**
128
+ * Schema attrs first; `ModelConfig.fields[k]` replaces that key entirely
129
+ * when present (compat override).
130
+ */
131
+ declare const resolveModelFields: (input: ModelInput) => Record<string, FieldMeta> | undefined;
132
+ /** What an op subscription hands back: `v.on` entries to mount. */
133
+ type SubscriptionEntries = OnEntry<string> | readonly OnEntry<string>[];
134
+ /**
135
+ * A model declared WITH its persistence: `schema` is the var (the shape),
136
+ * `fields` carries per-field storage metadata, and each op key SUBSCRIBES
137
+ * that op to app events - handed the bound collection op, it returns `v.on`
138
+ * entries that mount wherever the storage does (`use: [db]`). An
139
+ * already-built entry works in place of the fn.
140
+ */
141
+ type ModelConfig<SV extends AnyVar = AnyVar> = {
142
+ schema: SV;
143
+ fields?: { [F in keyof NonNullable<ValueOfVar<SV>>]?: FieldMeta; };
144
+ } & { [Op in StorageOp]?: ((action: Collection<NonNullable<ValueOfVar<SV>>>[Op]) => SubscriptionEntries) | SubscriptionEntries; };
145
+ /** A model is a bare var, or a config carrying the var as `schema`. */
146
+ type ModelInput = AnyVar | ModelConfig;
147
+ /** The var behind a model input. Checked through `$var`, never `schema` -
148
+ * a bare var also HAS a `schema` property (its type shape). */
149
+ type SchemaOf<T> = T extends {
150
+ $var: true;
151
+ } ? T : T extends {
152
+ schema: infer SV;
153
+ } ? SV : never;
154
+ type RowOf<T> = NonNullable<ValueOfVar<SchemaOf<T>>>;
155
+ /** Declared name of the var behind a model input - brands the collection. */
156
+ type ModelVarName<T> = NameOfVar<SchemaOf<T>> & string;
157
+ type StorageModels = Record<string, ModelInput>;
158
+ type StorageOp = "create" | "findOne" | "findMany" | "update" | "delete" | "count" | "consumeOne" | "incrementOne";
159
+ /** What a storage hook sees: which model and op, with the op's arguments
160
+ * positionally (`create` -> [data], `update` -> [where, patch], `findMany`
161
+ * -> [where, options], `incrementOne` -> [where, increments], ...). */
162
+ type StorageHookContext = {
163
+ /** The model KEY being addressed (the storage's property name). */
164
+ model: string;
165
+ op: StorageOp;
166
+ args: readonly unknown[];
167
+ };
168
+ /** `next()` runs the op (hooks below it included) and resolves its result;
169
+ * the hook's own return value IS the op's result - wrap, veto, transform. */
170
+ type StorageHook = (c: StorageHookContext, next: () => Promise<unknown>) => unknown;
171
+ /** Every target a storage hook can name - a flat union, so editors offer
172
+ * the whole surface: exact ("user.create"), per-model ("user.*"), per-op
173
+ * ("*.create"), everything ("*"). */
174
+ type StorageTarget<M> = "*" | `${keyof M & string}.${StorageOp | "*"}` | `*.${StorageOp}`;
175
+ type Storage<M extends StorageModels> = { [K in keyof M]: Collection<RowOf<M[K]>, ModelVarName<M[K]>>; } & StorageApi<M>;
176
+ /** Duck-type a storage instance - `$models` plus the `$adapter` method. */
177
+ declare const isStorage: (value: unknown) => value is Storage<StorageModels>;
178
+ /** The customization surface, `$`-prefixed so model keys never collide. */
179
+ type StorageApi<M extends StorageModels> = {
180
+ /** Swap the backend IN PLACE: every view of this storage - and every
181
+ * module that captured it - starts hitting the new adapter. */
182
+ $adapter: (adapter: StorageAdapter) => Storage<M>;
183
+ /** Intercept ops: hooks stack in mount order and apply to every view
184
+ * sharing this storage's state. */
185
+ $on: (target: StorageTarget<M>, hook: StorageHook) => Storage<M>;
186
+ /** A view WITHOUT these models - same adapter, same hooks. */
187
+ $omit: <K extends keyof M & string>(...keys: K[]) => Storage<Omit<M, K>>;
188
+ /** A view of ONLY these models - same adapter, same hooks. */
189
+ $pick: <K extends keyof M & string>(...keys: K[]) => Storage<Pick<M, K>>;
190
+ /** A view with MORE models - same adapter, same hooks. */
191
+ $extend: <M2 extends StorageModels>(models: M2) => Storage<M & M2>;
192
+ /** Run `fn` against a view whose ops share ONE adapter transaction -
193
+ * committed when it resolves, rolled back when it throws. Same models,
194
+ * same hooks (they run inside). An adapter without `transaction` runs
195
+ * `fn` plainly - no atomicity, same answer. */
196
+ $transaction: <T>(fn: (tx: Storage<M>) => Promise<T> | T) => Promise<T>;
197
+ /** The model definitions this view exposes. */
198
+ $models: M;
199
+ };
200
+ /**
201
+ * MANY instances of a var: each model is a collection of rows shaped like
202
+ * the var's VALUE, addressed by the var's NAME - the var stays what it
203
+ * always was (the scope's one current instance), the storage holds every
204
+ * other one, and the query API is how rows move between the two.
205
+ *
206
+ * The returned storage is CUSTOMIZABLE through its `$` surface: `$adapter`
207
+ * swaps the backend in place, `$on` mounts hooks around ops,
208
+ * `$omit`/`$pick`/`$extend` derive model views over the same state, and
209
+ * `$transaction` scopes ops to one adapter transaction.
210
+ *
211
+ * A model can also DECLARE its persistence: pass `{ schema, create: ... }`
212
+ * instead of the bare var, and the op subscriptions ride on the storage as
213
+ * mountable `v.on` entries - `use: [db]` wires them into the app. The same
214
+ * config carries `fields` metadata (unique, index, references) for schema
215
+ * generators to consume.
216
+ */
217
+ declare const makeStorage: <const M extends StorageModels>(adapter: StorageAdapter, models: M) => Storage<M>;
218
+ //#endregion
219
+ export { Collection, Condition, FieldMeta, FindManyOptions, ModelConfig, Storage, StorageAdapter, StorageApi, StorageHook, StorageHookContext, StorageModels, StorageOp, StorageTarget, Where, WhereOp, WhereOps, conditionsOf, fieldsFromSchema, isStorage, makeStorage, matchesWhere, memoryAdapter, resolveModelFields };
220
+ //# sourceMappingURL=storage.d.cts.map
@@ -0,0 +1,220 @@
1
+ import { OnEntry } from "./module.mjs";
2
+ import { NameOfVar, ValueOfVar, VarDefination } from "./var.mjs";
3
+ //#region src/storage.d.ts
4
+ /** Per-field operators, for everything equality can't say: expiry sweeps
5
+ * (`lt`), revocation lists (`in`), guarded counters (`lt` as the guard).
6
+ * A bare value stays plain equality - the common case reads like data. */
7
+ type WhereOps<V> = {
8
+ eq?: V;
9
+ ne?: V;
10
+ lt?: V;
11
+ lte?: V;
12
+ gt?: V;
13
+ gte?: V;
14
+ in?: readonly V[];
15
+ notIn?: readonly V[];
16
+ contains?: string;
17
+ startsWith?: string;
18
+ endsWith?: string;
19
+ };
20
+ type WhereOp = keyof WhereOps<unknown>;
21
+ /** AND across fields; each field a bare value (equality) or operators. */
22
+ type Where<R> = { [K in keyof R]?: R[K] | WhereOps<R[K]>; };
23
+ /** One normalized clause: `{ tag: "a" }` -> `{ field: "tag", op: "eq" }`. */
24
+ type Condition = {
25
+ field: string;
26
+ op: WhereOp;
27
+ value: unknown;
28
+ };
29
+ /** Flatten a where to normalized conditions - the adapter author's
30
+ * translation seam: map each condition onto your query language, AND them. */
31
+ declare const conditionsOf: (where?: Record<string, unknown>) => Condition[];
32
+ /** Does a row satisfy a where? The in-memory evaluator - the dummy adapter
33
+ * runs on it, and any adapter over an unqueryable backend can too. */
34
+ declare const matchesWhere: (row: Record<string, unknown>, where?: Record<string, unknown>) => boolean;
35
+ /** Shaping for `findMany`: sort, then window. */
36
+ type FindManyOptions<R> = {
37
+ limit?: number;
38
+ offset?: number;
39
+ sortBy?: {
40
+ field: keyof R & string;
41
+ direction?: "asc" | "desc";
42
+ };
43
+ };
44
+ /** One model's CRUD surface. `create` is generic so a row EXTENDED by a
45
+ * mounted module (an account with a password field) keeps its extra
46
+ * fields through the round-trip outside a widened scope. Inside a scope
47
+ * that mounts `v.extend` / same-name customize on the model var, {@link
48
+ * WidenSchemaFns} rewrites `R` (see `$modelVar`) the same way it widens
49
+ * `v.fn.type({ input: user })`. */
50
+ type Collection<R, N extends string = string> = {
51
+ create: <T extends R>(data: T) => Promise<T>;
52
+ findOne: (where: Where<R>) => Promise<R | null>;
53
+ findMany: (where?: Where<R>, options?: FindManyOptions<R>) => Promise<R[]>;
54
+ /** Merge `patch` into the FIRST match - null when nothing matched. */
55
+ update: (where: Where<R>, patch: Partial<R>) => Promise<R | null>;
56
+ /** Remove every match; how many is the answer. */
57
+ delete: (where: Where<R>) => Promise<number>;
58
+ count: (where?: Where<R>) => Promise<number>;
59
+ /** Claim the FIRST match: the row comes back and is GONE, atomically -
60
+ * two racing consumers never both get it. Single-use credentials
61
+ * (verification tokens, one-time codes) hang off this. */
62
+ consumeOne: (where: Where<R>) => Promise<R | null>;
63
+ /** Add to numeric fields of the FIRST match, atomically; the where is
64
+ * also the GUARD (`count: { lt: max }`), so check-and-bump is one op -
65
+ * null means no row passed it. Rate limiting hangs off this. */
66
+ incrementOne: (where: Where<R>, increments: Partial<Record<keyof R & string, number>>) => Promise<R | null>;
67
+ /**
68
+ * Phantom: the model var's name. Scope resolution reads it to WIDEN
69
+ * `R` with everything the scope mounts on that var - same role as
70
+ * `$fnVar` on fn schemas. Optional, so plain collection objects still
71
+ * satisfy the type.
72
+ */
73
+ readonly $modelVar?: [N];
74
+ };
75
+ /**
76
+ * What a real database implements: six required verbs, models addressed by
77
+ * NAME, `where` as bare-equality fields and/or operator objects
78
+ * (`conditionsOf` normalizes either form). The storage API above never
79
+ * changes - adapters translate it, sync or async.
80
+ *
81
+ * The optional verbs are ATOMICITY upgrades: without `consumeOne` /
82
+ * `incrementOne` the storage falls back to find-then-write - correct alone,
83
+ * racy under contention - and without `transaction` a `$transaction` block
84
+ * runs plainly. Implement them where the backend has the primitive.
85
+ */
86
+ type StorageAdapter = {
87
+ create: (model: string, data: Record<string, unknown>) => unknown;
88
+ findOne: (model: string, where: Record<string, unknown>) => unknown;
89
+ findMany: (model: string, where?: Record<string, unknown>, options?: FindManyOptions<Record<string, unknown>>) => unknown;
90
+ update: (model: string, where: Record<string, unknown>, patch: Record<string, unknown>) => unknown;
91
+ delete: (model: string, where: Record<string, unknown>) => unknown;
92
+ count: (model: string, where?: Record<string, unknown>) => unknown;
93
+ /** Atomic find-and-delete of the first match. */
94
+ consumeOne?: (model: string, where: Record<string, unknown>) => unknown;
95
+ /** Atomic guarded add to numeric fields of the first match. */
96
+ incrementOne?: (model: string, where: Record<string, unknown>, increments: Record<string, number>) => unknown;
97
+ /** Run `run` against a transaction-bound view of this adapter:
98
+ * committed when it resolves, rolled back when it throws. */
99
+ transaction?: <T>(run: (tx: StorageAdapter) => Promise<T>) => Promise<T>;
100
+ };
101
+ /** The DUMMY adapter: rows in arrays, one per model. Implements the whole
102
+ * surface - the optional verbs by mutation (single-threaded, so "atomic"),
103
+ * transactions by snapshot-and-restore. */
104
+ declare const memoryAdapter: () => StorageAdapter;
105
+ type AnyVar = VarDefination<any, any, any, any>;
106
+ /** Persistence facts about one field the ROW SHAPE can't say - consumed by
107
+ * schema generators and adapters, never acted on by the runtime. (Read
108
+ * shaping like redaction is a `$on` hook, not metadata.) Prefer declaring
109
+ * these via `db.unique` / `db.indexed` / `db.references` / `db.id` on the
110
+ * field schema; `ModelConfig.fields` remains an override. */
111
+ type FieldMeta = {
112
+ /** Primary key for this model. */
113
+ id?: boolean;
114
+ /** No two rows share a value. */
115
+ unique?: boolean;
116
+ /** Worth an index. */
117
+ index?: boolean;
118
+ /** Foreign key: this field holds `model.field` values. */
119
+ references?: {
120
+ model: string;
121
+ field: string;
122
+ onDelete?: "cascade" | "set null" | "restrict";
123
+ };
124
+ };
125
+ /** Read `$attrs.db` off each field of a var's object schema. */
126
+ declare const fieldsFromSchema: (sv: AnyVar) => Record<string, FieldMeta>;
127
+ /**
128
+ * Schema attrs first; `ModelConfig.fields[k]` replaces that key entirely
129
+ * when present (compat override).
130
+ */
131
+ declare const resolveModelFields: (input: ModelInput) => Record<string, FieldMeta> | undefined;
132
+ /** What an op subscription hands back: `v.on` entries to mount. */
133
+ type SubscriptionEntries = OnEntry<string> | readonly OnEntry<string>[];
134
+ /**
135
+ * A model declared WITH its persistence: `schema` is the var (the shape),
136
+ * `fields` carries per-field storage metadata, and each op key SUBSCRIBES
137
+ * that op to app events - handed the bound collection op, it returns `v.on`
138
+ * entries that mount wherever the storage does (`use: [db]`). An
139
+ * already-built entry works in place of the fn.
140
+ */
141
+ type ModelConfig<SV extends AnyVar = AnyVar> = {
142
+ schema: SV;
143
+ fields?: { [F in keyof NonNullable<ValueOfVar<SV>>]?: FieldMeta; };
144
+ } & { [Op in StorageOp]?: ((action: Collection<NonNullable<ValueOfVar<SV>>>[Op]) => SubscriptionEntries) | SubscriptionEntries; };
145
+ /** A model is a bare var, or a config carrying the var as `schema`. */
146
+ type ModelInput = AnyVar | ModelConfig;
147
+ /** The var behind a model input. Checked through `$var`, never `schema` -
148
+ * a bare var also HAS a `schema` property (its type shape). */
149
+ type SchemaOf<T> = T extends {
150
+ $var: true;
151
+ } ? T : T extends {
152
+ schema: infer SV;
153
+ } ? SV : never;
154
+ type RowOf<T> = NonNullable<ValueOfVar<SchemaOf<T>>>;
155
+ /** Declared name of the var behind a model input - brands the collection. */
156
+ type ModelVarName<T> = NameOfVar<SchemaOf<T>> & string;
157
+ type StorageModels = Record<string, ModelInput>;
158
+ type StorageOp = "create" | "findOne" | "findMany" | "update" | "delete" | "count" | "consumeOne" | "incrementOne";
159
+ /** What a storage hook sees: which model and op, with the op's arguments
160
+ * positionally (`create` -> [data], `update` -> [where, patch], `findMany`
161
+ * -> [where, options], `incrementOne` -> [where, increments], ...). */
162
+ type StorageHookContext = {
163
+ /** The model KEY being addressed (the storage's property name). */
164
+ model: string;
165
+ op: StorageOp;
166
+ args: readonly unknown[];
167
+ };
168
+ /** `next()` runs the op (hooks below it included) and resolves its result;
169
+ * the hook's own return value IS the op's result - wrap, veto, transform. */
170
+ type StorageHook = (c: StorageHookContext, next: () => Promise<unknown>) => unknown;
171
+ /** Every target a storage hook can name - a flat union, so editors offer
172
+ * the whole surface: exact ("user.create"), per-model ("user.*"), per-op
173
+ * ("*.create"), everything ("*"). */
174
+ type StorageTarget<M> = "*" | `${keyof M & string}.${StorageOp | "*"}` | `*.${StorageOp}`;
175
+ type Storage<M extends StorageModels> = { [K in keyof M]: Collection<RowOf<M[K]>, ModelVarName<M[K]>>; } & StorageApi<M>;
176
+ /** Duck-type a storage instance - `$models` plus the `$adapter` method. */
177
+ declare const isStorage: (value: unknown) => value is Storage<StorageModels>;
178
+ /** The customization surface, `$`-prefixed so model keys never collide. */
179
+ type StorageApi<M extends StorageModels> = {
180
+ /** Swap the backend IN PLACE: every view of this storage - and every
181
+ * module that captured it - starts hitting the new adapter. */
182
+ $adapter: (adapter: StorageAdapter) => Storage<M>;
183
+ /** Intercept ops: hooks stack in mount order and apply to every view
184
+ * sharing this storage's state. */
185
+ $on: (target: StorageTarget<M>, hook: StorageHook) => Storage<M>;
186
+ /** A view WITHOUT these models - same adapter, same hooks. */
187
+ $omit: <K extends keyof M & string>(...keys: K[]) => Storage<Omit<M, K>>;
188
+ /** A view of ONLY these models - same adapter, same hooks. */
189
+ $pick: <K extends keyof M & string>(...keys: K[]) => Storage<Pick<M, K>>;
190
+ /** A view with MORE models - same adapter, same hooks. */
191
+ $extend: <M2 extends StorageModels>(models: M2) => Storage<M & M2>;
192
+ /** Run `fn` against a view whose ops share ONE adapter transaction -
193
+ * committed when it resolves, rolled back when it throws. Same models,
194
+ * same hooks (they run inside). An adapter without `transaction` runs
195
+ * `fn` plainly - no atomicity, same answer. */
196
+ $transaction: <T>(fn: (tx: Storage<M>) => Promise<T> | T) => Promise<T>;
197
+ /** The model definitions this view exposes. */
198
+ $models: M;
199
+ };
200
+ /**
201
+ * MANY instances of a var: each model is a collection of rows shaped like
202
+ * the var's VALUE, addressed by the var's NAME - the var stays what it
203
+ * always was (the scope's one current instance), the storage holds every
204
+ * other one, and the query API is how rows move between the two.
205
+ *
206
+ * The returned storage is CUSTOMIZABLE through its `$` surface: `$adapter`
207
+ * swaps the backend in place, `$on` mounts hooks around ops,
208
+ * `$omit`/`$pick`/`$extend` derive model views over the same state, and
209
+ * `$transaction` scopes ops to one adapter transaction.
210
+ *
211
+ * A model can also DECLARE its persistence: pass `{ schema, create: ... }`
212
+ * instead of the bare var, and the op subscriptions ride on the storage as
213
+ * mountable `v.on` entries - `use: [db]` wires them into the app. The same
214
+ * config carries `fields` metadata (unique, index, references) for schema
215
+ * generators to consume.
216
+ */
217
+ declare const makeStorage: <const M extends StorageModels>(adapter: StorageAdapter, models: M) => Storage<M>;
218
+ //#endregion
219
+ export { Collection, Condition, FieldMeta, FindManyOptions, ModelConfig, Storage, StorageAdapter, StorageApi, StorageHook, StorageHookContext, StorageModels, StorageOp, StorageTarget, Where, WhereOp, WhereOps, conditionsOf, fieldsFromSchema, isStorage, makeStorage, matchesWhere, memoryAdapter, resolveModelFields };
220
+ //# sourceMappingURL=storage.d.mts.map