lecodes-sdk 2.0.7 → 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 +4 -0
- package/dist/types/inject.d.ts +1 -0
- package/dist/types/runtime/rpc.d.ts +2 -0
- package/dist/types/server/context.d.ts +4 -0
- package/dist/types/server/db/defineDb.d.ts +1 -1
- package/dist/types/server/db/fields.d.ts +25 -1
- package/dist/types/server/db/marci/query.d.ts +39 -2
- package/dist/types/server/db/types.d.ts +13 -7
- package/dist/types/server/files/db.d.ts +39 -0
- package/dist/types/server/files/models.d.ts +131 -0
- package/dist/types/server/inject.d.ts +1 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +3 -2
- package/src/compile/libraryImports.ts +9 -0
- package/src/compile/serverTypes.ts +6 -0
- package/src/inject.ts +1 -0
- package/src/runtime/rpc.ts +15 -3
- package/src/server/context.ts +4 -0
- package/src/server/db/defineDb.ts +62 -9
- package/src/server/db/fields.ts +21 -1
- package/src/server/db/httpTransport.ts +9 -1
- package/src/server/db/marci/query.ts +71 -4
- package/src/server/db/types.ts +16 -5
- package/src/server/files/db.ts +182 -0
- package/src/server/files/host.ts +496 -0
- package/src/server/files/models.ts +116 -0
- package/src/server/host.ts +17 -4
- package/src/server/inject.ts +1 -0
- package/src/server/runtime.ts +41 -0
- package/src/server/validate.ts +5 -0
- package/src/version.ts +1 -1
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 {}
|
package/dist/types/inject.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
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> = (
|
|
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?: (
|
|
122
|
-
$max?: (
|
|
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;
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "2.0.
|
|
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
|