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.
- package/LICENSE +21 -0
- package/README.md +265 -0
- package/dist/capability.cjs +417 -0
- package/dist/capability.cjs.map +1 -0
- package/dist/capability.d.cts +189 -0
- package/dist/capability.d.mts +189 -0
- package/dist/capability.mjs +401 -0
- package/dist/capability.mjs.map +1 -0
- package/dist/error.cjs +76 -0
- package/dist/error.cjs.map +1 -0
- package/dist/error.d.cts +51 -0
- package/dist/error.d.mts +51 -0
- package/dist/error.mjs +72 -0
- package/dist/error.mjs.map +1 -0
- package/dist/fn.cjs +350 -0
- package/dist/fn.cjs.map +1 -0
- package/dist/fn.d.cts +378 -0
- package/dist/fn.d.mts +378 -0
- package/dist/fn.mjs +350 -0
- package/dist/fn.mjs.map +1 -0
- package/dist/index.cjs +43 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +50 -0
- package/dist/index.d.mts +50 -0
- package/dist/index.mjs +24 -0
- package/dist/index.mjs.map +1 -0
- package/dist/module.cjs +176 -0
- package/dist/module.cjs.map +1 -0
- package/dist/module.d.cts +283 -0
- package/dist/module.d.mts +283 -0
- package/dist/module.mjs +166 -0
- package/dist/module.mjs.map +1 -0
- package/dist/plugins/db.cjs +24 -0
- package/dist/plugins/db.cjs.map +1 -0
- package/dist/plugins/db.d.cts +18 -0
- package/dist/plugins/db.d.mts +18 -0
- package/dist/plugins/db.mjs +19 -0
- package/dist/plugins/db.mjs.map +1 -0
- package/dist/plugins/http/attrs.cjs +91 -0
- package/dist/plugins/http/attrs.cjs.map +1 -0
- package/dist/plugins/http/attrs.d.cts +26 -0
- package/dist/plugins/http/attrs.d.mts +26 -0
- package/dist/plugins/http/attrs.mjs +87 -0
- package/dist/plugins/http/attrs.mjs.map +1 -0
- package/dist/plugins/http/cookie.cjs +117 -0
- package/dist/plugins/http/cookie.cjs.map +1 -0
- package/dist/plugins/http/cookie.d.cts +637 -0
- package/dist/plugins/http/cookie.d.mts +637 -0
- package/dist/plugins/http/cookie.mjs +113 -0
- package/dist/plugins/http/cookie.mjs.map +1 -0
- package/dist/plugins/http/error.cjs +39 -0
- package/dist/plugins/http/error.cjs.map +1 -0
- package/dist/plugins/http/error.d.cts +38 -0
- package/dist/plugins/http/error.d.mts +38 -0
- package/dist/plugins/http/error.mjs +35 -0
- package/dist/plugins/http/error.mjs.map +1 -0
- package/dist/plugins/http/handle.cjs +73 -0
- package/dist/plugins/http/handle.cjs.map +1 -0
- package/dist/plugins/http/handle.d.cts +955 -0
- package/dist/plugins/http/handle.d.mts +955 -0
- package/dist/plugins/http/handle.mjs +72 -0
- package/dist/plugins/http/handle.mjs.map +1 -0
- package/dist/plugins/http/redirect.cjs +82 -0
- package/dist/plugins/http/redirect.cjs.map +1 -0
- package/dist/plugins/http/redirect.d.cts +67 -0
- package/dist/plugins/http/redirect.d.mts +67 -0
- package/dist/plugins/http/redirect.mjs +79 -0
- package/dist/plugins/http/redirect.mjs.map +1 -0
- package/dist/plugins/http/request.cjs +63 -0
- package/dist/plugins/http/request.cjs.map +1 -0
- package/dist/plugins/http/request.d.cts +142 -0
- package/dist/plugins/http/request.d.mts +142 -0
- package/dist/plugins/http/request.mjs +61 -0
- package/dist/plugins/http/request.mjs.map +1 -0
- package/dist/plugins/http/response.cjs +13 -0
- package/dist/plugins/http/response.cjs.map +1 -0
- package/dist/plugins/http/response.d.cts +18 -0
- package/dist/plugins/http/response.d.mts +18 -0
- package/dist/plugins/http/response.mjs +13 -0
- package/dist/plugins/http/response.mjs.map +1 -0
- package/dist/plugins/http.cjs +64 -0
- package/dist/plugins/http.cjs.map +1 -0
- package/dist/plugins/http.d.cts +945 -0
- package/dist/plugins/http.d.mts +945 -0
- package/dist/plugins/http.mjs +37 -0
- package/dist/plugins/http.mjs.map +1 -0
- package/dist/plugins/read-only.cjs +19 -0
- package/dist/plugins/read-only.cjs.map +1 -0
- package/dist/plugins/read-only.d.cts +17 -0
- package/dist/plugins/read-only.d.mts +17 -0
- package/dist/plugins/read-only.mjs +19 -0
- package/dist/plugins/read-only.mjs.map +1 -0
- package/dist/schema.cjs +245 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.d.cts +411 -0
- package/dist/schema.d.mts +411 -0
- package/dist/schema.mjs +235 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/scope.d.cts +31 -0
- package/dist/scope.d.mts +31 -0
- package/dist/storage.cjs +308 -0
- package/dist/storage.cjs.map +1 -0
- package/dist/storage.d.cts +220 -0
- package/dist/storage.d.mts +220 -0
- package/dist/storage.mjs +302 -0
- package/dist/storage.mjs.map +1 -0
- package/dist/types.d.cts +8 -0
- package/dist/types.d.mts +8 -0
- package/dist/var.cjs +186 -0
- package/dist/var.cjs.map +1 -0
- package/dist/var.d.cts +46 -0
- package/dist/var.d.mts +46 -0
- package/dist/var.mjs +177 -0
- package/dist/var.mjs.map +1 -0
- 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
|