lecodes-sdk 2.0.6 → 2.0.8

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/global.d.ts CHANGED
@@ -480,6 +480,8 @@ declare global {
480
480
  type StepHandler = SDK.StepHandler
481
481
  type StepWarpOptions = SDK.StepWarpOptions
482
482
  type StopOptions = SDK.StopOptions
483
+ type StoredFile = SDK.StoredFile
484
+ type StoredImage = SDK.StoredImage
483
485
  type System<K extends string, S extends SystemHost = Scene, E extends EventMap = {}> = SDK.System<K, S, E>
484
486
  type SystemHost = SDK.SystemHost
485
487
  type TabDef = SDK.TabDef
@@ -580,6 +582,8 @@ declare global {
580
582
  const request: typeof SERVER.request
581
583
  const t: typeof SERVER.t
582
584
  type ApiError = SERVER.ApiError
585
+ type StoredFile = SERVER.StoredFile
586
+ type StoredImage = SERVER.StoredImage
583
587
  }
584
588
 
585
589
  export {}
@@ -23,6 +23,7 @@ export { WebSocket } from "./runtime/net";
23
23
  export { Net, NetPlayer, Replicated, NetEntity, type NetMessage, type NetRole, type NetStatus, type NetEvents, type NetLaunch, type NetInput, type NetKind, type NetTransform, type NetCorrection } from "./net";
24
24
  export { __rpc, __channel, __serverOnly, RpcError } from "./runtime/rpc";
25
25
  export type { ChannelSubscription } from "./server/channel";
26
+ export type { StoredFile, StoredImage } from "./server/files/models";
26
27
  export { AudioPlayer, VideoPlayer } from "./runtime/media";
27
28
  export { Sound, type SoundOptions } from "./audio/Sound";
28
29
  export { Voice, type VoiceEvents } from "./audio/Voice";
@@ -9,6 +9,8 @@
9
9
  *
10
10
  * Wire (implemented by the runner):
11
11
  * POST <serverUrl>/api/<id> body {"args":[…]}, `authorization: Bearer <session>` when known
12
+ * — with a `File` among the arguments: multipart, the field `args` is
13
+ * that JSON with `{"$file": <i>}` where the file stood, `file<i>` the files
12
14
  * → 200 {"ok":true,"result":…,"session"?:"…"} | {"ok":false,"status":n,"message":"…","session"?:"…"}
13
15
  * WS <serverUrl>/ws the channel socket — its frames are ./wire.ts
14
16
  *
@@ -14,6 +14,10 @@ export type RequestContext = {
14
14
  sessionToken?: string;
15
15
  /** Set by the auth runtime after resolving the token — read via `db.auth`, not here. */
16
16
  auth?: unknown;
17
+ /** The files a multipart call carried, as the receiving process left them (./files/host.ts `Upload`). */
18
+ uploads?: unknown[];
19
+ /** What `/files/<id>/<name>` is appended to in a stored file's url: the backend's own base, as this request reached it. */
20
+ filesBase?: string;
17
21
  };
18
22
  type Provider = () => RequestContext | undefined;
19
23
  /** Host hook (runner / test harness): install the current-request provider. */
@@ -7,7 +7,7 @@
7
7
  * `defineDb(models, { transport })` or provided by the host through `setDbTransport()` — user code
8
8
  * never sees a connection string.
9
9
  */
10
- import type { FieldDef } from "./fields";
10
+ import { type FieldDef } from "./fields";
11
11
  import { type MarciTransport } from "./marci/query";
12
12
  import type { Db, Fields, Model, Schema, ValidateRefs } from "./types";
13
13
  export declare const model: <F extends Fields>(fields: F) => Model<F>;
@@ -6,8 +6,9 @@
6
6
  * mutated), so `const s = t.string(); { a: s, b: s.optional() }` behaves as expected.
7
7
  */
8
8
  import type { JsonValue } from "./marci/query";
9
+ import type { StoredFile, StoredImage } from "../files/models";
9
10
  export type ScalarKind = "string" | "int" | "float" | "bool" | "date" | "uuid" | "";
10
- export type FieldKind = "scalar" | "json" | "enum" | "struct" | "one" | "many" | "list";
11
+ export type FieldKind = "scalar" | "json" | "enum" | "struct" | "one" | "many" | "list" | "file";
11
12
  export type OnDelete = "cascade" | "setNull" | "restrict";
12
13
  /** Phantom metadata — the type-level twin of `FieldDef`. */
13
14
  export type FieldMeta = {
@@ -58,6 +59,10 @@ export type FieldDef = {
58
59
  fields?: Record<string, Field<any, any>>;
59
60
  /** one / many / list */
60
61
  model?: string;
62
+ /** file: a picture (`t.image()`) — `max` is the most its longer side may be, px. */
63
+ image?: {
64
+ max: number;
65
+ };
61
66
  /** many: the field on the related model that holds the reference (`@bind`); resolved by defineDb when omitted. */
62
67
  back?: string;
63
68
  };
@@ -136,6 +141,25 @@ export declare const t: {
136
141
  kind: "many";
137
142
  model: N;
138
143
  }>>;
144
+ /**
145
+ * A stored file: written as a `File` (an upload — an endpoint's parameter), read as
146
+ * `{ url, name, type, size, width?, height? }`. `.optional()` = may be empty, `.array()` = an ordered
147
+ * list of files. It is deleted with its row — or when the field is given another file, or `null`.
148
+ */
149
+ file: () => Field<StoredFile, Meta<{
150
+ kind: "file";
151
+ }>>;
152
+ /**
153
+ * A stored PICTURE — a `t.file()` that takes images only and keeps them fit to show: turned the way
154
+ * the camera meant, without its metadata (a photo's place and time), as WebP, and no larger than
155
+ * `max` px on its longer side (2048 when not said; a picture is never enlarged).
156
+ * Read as a `StoredFile` whose `width` and `height` are always there. A GIF is kept as it is.
157
+ */
158
+ image: (options?: {
159
+ max?: number;
160
+ }) => Field<StoredImage, Meta<{
161
+ kind: "file";
162
+ }>>;
139
163
  /** Ordered relation list stored inline (`@list`) — hand-arranged collections, duplicates allowed. */
140
164
  list: <N extends string>(model: N) => Field<never, Meta<{
141
165
  kind: "list";
@@ -197,6 +197,15 @@ export type MarciOp = {
197
197
  query?: any;
198
198
  data?: any;
199
199
  id?: any;
200
+ journal?: JournalArgs;
201
+ };
202
+ /** The arguments of a journal action (`$journalOpen` / `$journalRead` / `$journalDrop`). `wait` is in seconds. */
203
+ export type JournalArgs = {
204
+ name: string;
205
+ on?: readonly JournalOp[];
206
+ after?: number;
207
+ limit?: number;
208
+ wait?: number;
200
209
  };
201
210
  export type MarciTransport = {
202
211
  exec(op: MarciOp): Promise<any>;
@@ -291,13 +300,41 @@ export interface Query<T extends ModelTypes, Sel = T["scalars"]> extends Promise
291
300
  /** @deprecated use `.select(query).first()` (removed in the next minor). */
292
301
  findFirst<Q extends T["query"] = {}>(query?: Q): Op<Rows<T, Q> | null>;
293
302
  }
294
- /** `db.<model>`: the root query, plus `reindex()` for models with a `@custom` (vector / full-text) index. */
303
+ /** What a journal can record. Only deletes so far. */
304
+ export type JournalOp = "delete";
305
+ /** One recorded change. `row` is the row as it last was: its id and every scalar field. */
306
+ export type JournalEntry<T extends ModelTypes, O extends JournalOp = JournalOp> = {
307
+ seq: number;
308
+ op: O;
309
+ row: Rows<T, T["scalars"]>;
310
+ };
311
+ export type JournalOptions<O extends JournalOp> = {
312
+ /** The operations to record. A journal that exists with other ones is an error, not a redefinition. */
313
+ on: O | readonly O[];
314
+ /** `false`: the loop ends when the journal is read through. By default it waits for the next entry. */
315
+ wait?: boolean;
316
+ };
317
+ /**
318
+ * A named, durable log of a model's changes. It starts recording when it is first asked for and keeps
319
+ * every entry until the loop that reads it has moved past it — an entry whose loop body threw, or that
320
+ * was never reached, is delivered again to the next reader of the same name.
321
+ */
322
+ export interface Journal<T extends ModelTypes, O extends JournalOp = JournalOp> extends AsyncIterable<JournalEntry<T, O>> {
323
+ /** Drops the journal with whatever it still holds; the model's writes stop paying for it. */
324
+ drop(): Promise<void>;
325
+ }
326
+ /**
327
+ * `db.<model>`: the root query, plus `reindex()` for models with a `@custom` (vector / full-text) index and
328
+ * `$journal(name, { on })` — the journal of this model's changes under that name, created on first use.
329
+ */
295
330
  export type Collection<T extends ModelTypes> = Query<T> & (T["reindex"] extends true ? {
296
331
  reindex(): Op<{
297
332
  ok: boolean;
298
333
  indexed: number;
299
334
  }>;
300
- } : {});
335
+ } : {}) & {
336
+ $journal<O extends JournalOp>(name: string, options: JournalOptions<O>): Journal<T, O>;
337
+ };
301
338
  /** One field of a model, in slot order: `n` name, `k` key | body | one | many, `m` the relation's target model. */
302
339
  export type FieldDesc = {
303
340
  n: string;
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import type { Field, FieldMeta } from "./fields";
10
10
  import type { IdentityPublicFields, SessionPublicFields } from "../auth/models";
11
+ import type { StoredFile, UploadedFile as File } from "../files/models";
11
12
  import type { CompareNumValue, CompareRefListValue, CompareRefValue, CompareStrValue, CompareValue, CustomSearchValue, FullTextSearch, JsonCondition, JsonPathWhere, Op, PrimitiveListUpdate, Query as MarciQuery, RefListUpdate, RefListUpdateOrdered, RefUpdate, RefUpdateStruct, Rows, Sub, UpdateNumValue, VectorSearch, WhereValue } from "./marci/query";
12
13
  export type Fields = Record<string, Field<any, any>>;
13
14
  export type Model<F extends Fields = Fields> = {
@@ -48,7 +49,12 @@ export type Row<S, N extends keyof S> = Id<S, N> & {
48
49
  type StructRow<S, F extends Fields> = {
49
50
  [K in keyof F]: FieldRow<S, F[K]>;
50
51
  };
52
+ /** What a query without a selection returns besides the id: the scalars, and the files. */
51
53
  type ScalarKeys<F extends Fields> = {
54
+ [K in keyof F]: MetaOf<F[K]>["kind"] extends "scalar" | "enum" | "json" | "file" ? K : never;
55
+ }[keyof F];
56
+ /** The fields a row is ordered and aggregated by. */
57
+ type ValueKeys<F extends Fields> = {
52
58
  [K in keyof F]: MetaOf<F[K]>["kind"] extends "scalar" | "enum" | "json" ? K : never;
53
59
  }[keyof F];
54
60
  type NumKeys<F extends Fields> = {
@@ -60,9 +66,9 @@ export type Scalars<S, N extends keyof S> = Pick<Row<S, N>, (ScalarKeys<FieldsOf
60
66
  export type ScalarSelect<S, N extends keyof S> = {
61
67
  [K in (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>]: true;
62
68
  };
63
- type FieldInsert<S, Fld, M extends FieldMeta = MetaOf<Fld>> = M["kind"] extends "one" ? Nullable<M, Id<S, ModelName<S, Fld>>> : M["kind"] extends "many" | "list" ? Id<S, ModelName<S, Fld>>[] : M["kind"] extends "struct" ? Nullable<M, StructInsert<S, StructFieldsOf<Fld>>> : Nullable<M, Arr<M, TsOf<Fld>>>;
69
+ type FieldInsert<S, Fld, M extends FieldMeta = MetaOf<Fld>> = M["kind"] extends "one" ? Nullable<M, Id<S, ModelName<S, Fld>>> : M["kind"] extends "many" | "list" ? Id<S, ModelName<S, Fld>>[] : M["kind"] extends "struct" ? Nullable<M, StructInsert<S, StructFieldsOf<Fld>>> : M["kind"] extends "file" ? Nullable<M, Arr<M, File>> : Nullable<M, Arr<M, TsOf<Fld>>>;
64
70
  type RequiredKeys<F extends Fields> = {
65
- [K in keyof F]: K extends IdKeys<F> ? never : MetaOf<F[K]>["optional"] extends true ? never : MetaOf<F[K]>["hasDefault"] extends true ? never : MetaOf<F[K]>["kind"] extends "many" | "list" ? never : K;
71
+ [K in keyof F]: K extends IdKeys<F> ? never : MetaOf<F[K]>["optional"] extends true ? never : MetaOf<F[K]>["hasDefault"] extends true ? never : MetaOf<F[K]>["kind"] extends "many" | "list" ? never : MetaOf<F[K]>["kind"] extends "file" ? (MetaOf<F[K]>["array"] extends true ? never : K) : K;
66
72
  }[keyof F];
67
73
  type Simplify<T> = {
68
74
  [K in keyof T]: T[K];
@@ -74,7 +80,7 @@ type InsertOf<S, F extends Fields> = Simplify<{
74
80
  }>;
75
81
  export type Insert<S, N extends keyof S> = InsertOf<S, FieldsOf<S[N]>>;
76
82
  type StructInsert<S, F extends Fields> = InsertOf<S, F>;
77
- type FieldUpdate<S, Fld, M extends FieldMeta = MetaOf<Fld>> = M["kind"] extends "one" ? Nullable<M, RefUpdate<Id<S, ModelName<S, Fld>>>> : M["kind"] extends "many" ? RefListUpdate<Id<S, ModelName<S, Fld>>> : M["kind"] extends "list" ? RefListUpdateOrdered<Id<S, ModelName<S, Fld>>> : M["kind"] extends "struct" ? RefUpdateStruct<StructInsert<S, StructFieldsOf<Fld>>, UpdateOf<S, StructFieldsOf<Fld>>> | null : M["array"] extends true ? TsOf<Fld>[] | PrimitiveListUpdate<TsOf<Fld>> : Nullable<M, TsOf<Fld> | (IsNum<M> extends true ? UpdateNumValue : never)>;
83
+ type FieldUpdate<S, Fld, M extends FieldMeta = MetaOf<Fld>> = M["kind"] extends "one" ? Nullable<M, RefUpdate<Id<S, ModelName<S, Fld>>>> : M["kind"] extends "many" ? RefListUpdate<Id<S, ModelName<S, Fld>>> : M["kind"] extends "list" ? RefListUpdateOrdered<Id<S, ModelName<S, Fld>>> : M["kind"] extends "struct" ? RefUpdateStruct<StructInsert<S, StructFieldsOf<Fld>>, UpdateOf<S, StructFieldsOf<Fld>>> | null : M["kind"] extends "file" ? Nullable<M, Arr<M, File | StoredFile>> : M["array"] extends true ? TsOf<Fld>[] | PrimitiveListUpdate<TsOf<Fld>> : Nullable<M, TsOf<Fld> | (IsNum<M> extends true ? UpdateNumValue : never)>;
78
84
  type UpdateOf<S, F extends Fields> = {
79
85
  [K in Exclude<keyof F, IdKeys<F>>]?: FieldUpdate<S, F[K]>;
80
86
  };
@@ -87,7 +93,7 @@ type StructSelect<S, F extends Fields> = {
87
93
  [K in keyof F]?: FieldSelect<S, F[K]>;
88
94
  };
89
95
  type ScalarWhere<T, M extends FieldMeta> = Nullable<M, CompareValue<T>> | (IsNum<M> extends true ? CompareNumValue<T> : M["scalar"] extends "string" ? CompareStrValue<T> : never);
90
- type FieldWhere<S, Fld, M extends FieldMeta = MetaOf<Fld>> = M["kind"] extends "one" ? CompareRefValue<Where<S, ModelName<S, Fld>> | null> : M["kind"] extends "many" | "list" ? CompareRefListValue<Where<S, ModelName<S, Fld>>> : M["kind"] extends "struct" ? CompareRefValue<WhereValue<StructWhereFields<S, StructFieldsOf<Fld>>> | null> : M["kind"] extends "json" ? JsonPathWhere | JsonCondition : M["array"] extends true ? TsOf<Fld>[] : ScalarWhere<TsOf<Fld>, M> | CustomSearchValue<FullTextSearch> | CustomSearchValue<VectorSearch>;
96
+ type FieldWhere<S, Fld, M extends FieldMeta = MetaOf<Fld>> = M["kind"] extends "one" ? CompareRefValue<Where<S, ModelName<S, Fld>> | null> : M["kind"] extends "many" | "list" ? CompareRefListValue<Where<S, ModelName<S, Fld>>> : M["kind"] extends "struct" ? CompareRefValue<WhereValue<StructWhereFields<S, StructFieldsOf<Fld>>> | null> : M["kind"] extends "json" ? JsonPathWhere | JsonCondition : M["kind"] extends "file" ? never : M["array"] extends true ? TsOf<Fld>[] : ScalarWhere<TsOf<Fld>, M> | CustomSearchValue<FullTextSearch> | CustomSearchValue<VectorSearch>;
91
97
  type WhereFieldsOf<S, F extends Fields> = {
92
98
  [K in keyof F]?: FieldWhere<S, F[K]>;
93
99
  };
@@ -96,7 +102,7 @@ type IdWhere<S, N extends keyof S> = {
96
102
  };
97
103
  export type Where<S, N extends keyof S> = WhereValue<WhereFieldsOf<S, FieldsOf<S[N]>> & IdWhere<S, N>>;
98
104
  type StructWhereFields<S, F extends Fields> = WhereFieldsOf<S, F>;
99
- type OrderKeys<S, N extends keyof S> = (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>;
105
+ type OrderKeys<S, N extends keyof S> = (ValueKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>;
100
106
  /** ONE field and its direction: the engine sorts by a single field, so a second key is a type error. */
101
107
  export type Order<S, N extends keyof S> = {
102
108
  [K in OrderKeys<S, N>]: {
@@ -118,8 +124,8 @@ export type AggregateQuery<S, N extends keyof S> = {
118
124
  $count?: true;
119
125
  $sum?: NumKeys<FieldsOf<S[N]>> & string;
120
126
  $avg?: NumKeys<FieldsOf<S[N]>> & string;
121
- $min?: (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & string;
122
- $max?: (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & string;
127
+ $min?: (ValueKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & string;
128
+ $max?: (ValueKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & string;
123
129
  };
124
130
  /** The per-model type bag marcidb's generic `Query<T>` is parametrised by (what its codegen emits as `UserTypes`). */
125
131
  export type Types<S, N extends keyof S> = {
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The db layer's half of stored files (./models.ts): what turns a file FIELD — a value in a row — into
3
+ * operations on the platform's `File` model, and back.
4
+ *
5
+ * read the field is selected as the reverse relation it is (the `File` rows that point at the
6
+ * row, in order) and comes back as a `StoredFile`, or a list of them
7
+ * insert the row is inserted without the field; each upload's `File` row is then pointed at it
8
+ * (`ref` to the insert's id) — one transaction
9
+ * update the row's files that are no longer listed are deleted, the uploads are pointed at it —
10
+ * one transaction with the update itself
11
+ *
12
+ * An upload written to a `t.image()` field is first made a picture fit to show (the host's work,
13
+ * ./host.ts): that is why a plan is awaited. What it became — its type, its size — goes to its row
14
+ * in the operation that attaches it.
15
+ *
16
+ * A file is written from the FILE's side (its reference to the owner): that is the side the database
17
+ * cascades over. Only an upload of the current request is ever attached: a `StoredFile` the app hands
18
+ * back means "keep this one", and one of another row is not moved — a client that knows the url of
19
+ * someone else's file cannot take it.
20
+ */
21
+ import type { MarciOp } from "../db/marci/query";
22
+ import { type FileField } from "./models";
23
+ type Plan = {
24
+ op: MarciOp;
25
+ after: MarciOp[];
26
+ };
27
+ export type FileLayer = {
28
+ /** The operation as the database runs it, and the ones that follow it in the same transaction.
29
+ * `index` is the operation's place in that transaction (what `ref` counts from). */
30
+ plan(op: MarciOp, index: number): Promise<Plan>;
31
+ /** The database's answer to a planned operation, as the project reads it. */
32
+ result(op: MarciOp, value: any): any;
33
+ };
34
+ /**
35
+ * `fields`: the schema's `t.file()` fields. `relations`: model → relation field → its target model.
36
+ * `scalars`: model → what a query without a selection returns (file fields included).
37
+ */
38
+ export declare const createFileLayer: (fields: FileField[], relations: Record<string, Record<string, string>>, scalars: Record<string, string[]>) => FileLayer;
39
+ export {};
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Stored files: `t.file()` on a model.
3
+ *
4
+ * A project never declares where a file lives. A model that has a `t.file()` field makes the db carry
5
+ * one more model, the PLATFORM's `File` — a row per stored file: what it is (name, type, size, an
6
+ * image's dimensions) and whose it is. The owner is a reference FROM the file TO its row, with
7
+ * `onDelete("cascade")`, one optional reference per `t.file()` field of the schema (`postImage`,
8
+ * `userAvatar`): deleting a post — by name, or because its author was deleted — deletes its files in
9
+ * the same transaction, by the database's own rule. The field on the owner is the reverse side of
10
+ * that reference.
11
+ *
12
+ * The bytes are the host's (./host.ts): a file on disk named by the row's id. They go only because
13
+ * the row went — the host reads the deletions from marcidb's journal of this model.
14
+ *
15
+ * What a project sees is a VALUE: it writes a `File` (an upload, an endpoint's parameter) and reads a
16
+ * `StoredFile`. The url is computed when the row is read, never stored.
17
+ */
18
+ import { type Field } from "../db/fields";
19
+ export declare const FILE_MODEL = "File";
20
+ /** A stored file as a project reads it. */
21
+ export type StoredFile = {
22
+ /** Where the bytes are served from. Public, not guessable; it never changes for this file. */
23
+ url: string;
24
+ name: string;
25
+ /** The media type: `image/jpeg`, `video/mp4`, `application/pdf`. */
26
+ type: string;
27
+ /** Bytes. */
28
+ size: number;
29
+ /** An image's dimensions in pixels (absent for anything else). */
30
+ width?: number;
31
+ height?: number;
32
+ };
33
+ /** A stored picture (`t.image()`): its size is always known. */
34
+ export type StoredImage = StoredFile & {
35
+ width: number;
36
+ height: number;
37
+ };
38
+ /**
39
+ * A file as an endpoint receives it — the app's `File` (a picked file, a photo), uploaded with the
40
+ * call. Written to a `t.file()` field, it is stored; an endpoint that writes it nowhere drops it.
41
+ */
42
+ export type UploadedFile = {
43
+ readonly name: string;
44
+ readonly size: number;
45
+ readonly type: string;
46
+ };
47
+ /** One `t.file()` field of the schema; `image` = it is a `t.image()`, with its rule. */
48
+ export type FileField = {
49
+ model: string;
50
+ key: string;
51
+ array: boolean;
52
+ owner: string;
53
+ image?: ImageRule;
54
+ };
55
+ export type ImageRule = {
56
+ max: number;
57
+ };
58
+ /** What a file became when it was made fit for an image field: the facts its row carries from then on. */
59
+ export type ImageFacts = {
60
+ name: string;
61
+ type: string;
62
+ size: number;
63
+ width: number;
64
+ height: number;
65
+ };
66
+ /** The reference on `File` that says "I am this field of that row": `Post.image` → `postImage`. */
67
+ export declare const fileOwnerKey: (model: string, key: string) => string;
68
+ /** What a data browser may show of a file (the rest is the platform's bookkeeping). */
69
+ export declare const filePublicFields: () => {
70
+ name: Field<string, import("../db/fields").Meta<{
71
+ kind: "scalar";
72
+ scalar: "string";
73
+ }>>;
74
+ type: Field<string, import("../db/fields").Meta<{
75
+ kind: "scalar";
76
+ scalar: "string";
77
+ }>>;
78
+ size: Field<number, import("../db/fields").Meta<{
79
+ kind: "scalar";
80
+ scalar: "int";
81
+ }>>;
82
+ width: Field<number, {
83
+ kind: "scalar";
84
+ scalar: "int";
85
+ optional: true;
86
+ hasDefault: false;
87
+ array: false;
88
+ isId: false;
89
+ model: "";
90
+ }>;
91
+ height: Field<number, {
92
+ kind: "scalar";
93
+ scalar: "int";
94
+ optional: true;
95
+ hasDefault: false;
96
+ array: false;
97
+ isId: false;
98
+ model: "";
99
+ }>;
100
+ createdAt: Field<number, {
101
+ kind: "scalar";
102
+ scalar: "date";
103
+ optional: false;
104
+ hasDefault: true;
105
+ array: false;
106
+ isId: false;
107
+ model: "";
108
+ }>;
109
+ };
110
+ /** The whole `File` of a schema with these `t.file()` fields. */
111
+ export declare const fileFields: (fields: FileField[]) => Record<string, Field<any, any>>;
112
+ /**
113
+ * What the host (./host.ts) gives the db layer of a bundle — a global, because the bundle carries its
114
+ * own copy of this module. Absent (a unit test without a host): nothing is an upload, urls are relative.
115
+ */
116
+ export type FilesSeam = {
117
+ /** The id of the `File` row behind an upload of the current request; undefined for anything else —
118
+ * a value the app sent as JSON is never an upload, whatever it looks like. */
119
+ uploaded(value: unknown): string | undefined;
120
+ url(id: string, name: string): string;
121
+ /** Make the upload `id` fit for a `t.image()` field (its bytes are rewritten in place) and say what
122
+ * it is now. Throws an `ApiError` for what is no picture. */
123
+ image(id: string, rule: ImageRule): Promise<ImageFacts>;
124
+ /** A write that may have removed rows of `File` went through the db (a delete, a rewritten file field): their bytes are due. */
125
+ dirty: boolean;
126
+ };
127
+ export declare const filesSeam: () => FilesSeam | undefined;
128
+ export declare const setFilesSeam: (value: FilesSeam | null) => void;
129
+ /** The row a `StoredFile` the app handed back stands for (by its url); undefined for anything else. */
130
+ export declare const storedFileId: (value: unknown) => string | undefined;
131
+ export declare const fileUrl: (id: string, name: string) => string;
@@ -2,3 +2,4 @@ export { t, model, defineDb, ref } from "./db";
2
2
  export { ApiError } from "./errors";
3
3
  export { channel } from "./channel";
4
4
  export { request } from "./context";
5
+ export type { StoredFile, StoredImage } from "./files/models";
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "2.0.6";
1
+ export declare const SDK_VERSION = "2.0.8";
2
2
  /** The major of a semver string, or null when it is not one. */
3
3
  export declare const sdkMajor: (version: string | null | undefined) => number | null;
4
4
  /** The `// sdk: <version>` line of a compiled bundle's header (sdk/src/compile/header.ts), read from