@phis/contracts 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/NOTICE +1 -1
  2. package/README.md +175 -67
  3. package/dist/access.d.ts +63 -0
  4. package/dist/access.js +78 -0
  5. package/dist/addon-index.d.ts +5 -0
  6. package/dist/addon-index.js +5 -0
  7. package/dist/addon.d.ts +181 -57
  8. package/dist/addon.js +2 -2
  9. package/dist/assets.d.ts +165 -0
  10. package/dist/assets.js +1 -0
  11. package/dist/capabilities.d.ts +175 -33
  12. package/dist/capabilities.js +2 -2
  13. package/dist/catalog.d.ts +104 -0
  14. package/dist/catalog.js +116 -0
  15. package/dist/cms.d.ts +120 -0
  16. package/dist/cms.js +270 -0
  17. package/dist/core.d.ts +52 -6
  18. package/dist/core.js +55 -8
  19. package/dist/http.d.ts +33 -0
  20. package/dist/http.js +33 -0
  21. package/dist/locale.d.ts +14 -0
  22. package/dist/locale.js +14 -0
  23. package/dist/logging.d.ts +49 -0
  24. package/dist/logging.js +17 -0
  25. package/dist/media-storage.d.ts +371 -0
  26. package/dist/media-storage.js +84 -0
  27. package/dist/media.d.ts +272 -0
  28. package/dist/media.js +421 -0
  29. package/dist/queries.d.ts +280 -48
  30. package/dist/queries.js +10 -1
  31. package/dist/resource-links.d.ts +94 -0
  32. package/dist/resource-links.js +27 -0
  33. package/dist/schema.d.ts +160 -26
  34. package/dist/schema.js +11 -2
  35. package/dist/server-capabilities.d.ts +37 -0
  36. package/dist/server-capabilities.js +17 -0
  37. package/dist/signals.d.ts +58 -0
  38. package/dist/signals.js +118 -0
  39. package/dist/site-groups.d.ts +53 -0
  40. package/dist/site-groups.js +54 -0
  41. package/dist/support.d.ts +390 -0
  42. package/dist/support.js +148 -0
  43. package/dist/threads.d.ts +363 -0
  44. package/dist/threads.js +146 -0
  45. package/dist/translation.d.ts +125 -0
  46. package/dist/translation.js +15 -0
  47. package/dist/user-state.d.ts +132 -0
  48. package/dist/user-state.js +92 -0
  49. package/package.json +64 -4
  50. package/dist/index.d.ts +0 -6
  51. package/dist/index.js +0 -6
package/dist/addon.d.ts CHANGED
@@ -11,35 +11,65 @@
11
11
  * no node builtins -- the Add-on states, and Core checks. If something here ever does more than hand
12
12
  * back a constant, it is in the wrong package.
13
13
  */
14
- import type { PhiServerAddonCapabilities, PhiServerAddonJobHandler, PhiServerSiteAccess } from "./capabilities.js";
15
- import type { PhiServerCoreCapabilityId, PhiServerServiceKind } from "./core.js";
16
- import type { PhiServerAddonQueryCatalog } from "./queries.js";
17
- import type { PhiServerAddonSchemaDescriptor } from "./schema.js";
18
- export declare const PHI_SERVER_ADDON_MANIFEST_VERSION: 1;
19
- export declare const PHI_SERVER_ADDON_ABI_VERSION: 1;
20
- export type PhiServerAddonAuthPolicy = "internal" | "site-user" | "site-admin" | "operator";
21
- export type PhiServerAddonHttpMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT";
22
- export type PhiServerAddonApiRouteDescriptor = {
14
+ import type { PhisAddonCapabilities, PhisAddonEventHandler, PhisAddonJobHandler, PhisSiteAccess } from "./capabilities.js";
15
+ import type { PhisAddonEventId, PhisCoreCapabilityId, PhisServiceKind } from "./core.js";
16
+ import type { PhisAddonQueryCatalog } from "./queries.js";
17
+ import type { PhisAddonSchemaDescriptor } from "./schema.js";
18
+ export declare const PHIS_ADDON_MANIFEST_VERSION: 1;
19
+ export declare const PHIS_ADDON_ABI_VERSION: 1;
20
+ export type PhisAddonAuthPolicy = "internal" | "site-user" | "site-admin" | "operator";
21
+ export type PhisAddonHttpMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT";
22
+ export type PhisAddonApiRouteDescriptor = {
23
23
  id: string;
24
- method: PhiServerAddonHttpMethod;
24
+ method: PhisAddonHttpMethod;
25
25
  path: string;
26
26
  handler: string;
27
- auth: PhiServerAddonAuthPolicy;
27
+ auth: PhisAddonAuthPolicy;
28
28
  siteScoped: boolean;
29
29
  bodyLimitBytes?: number;
30
30
  rateLimitClass?: string;
31
31
  timeoutMs?: number;
32
32
  };
33
- export type PhiServerAddonHookDescriptor = {
33
+ /**
34
+ * How Core establishes that a hook request is from who it claims to be.
35
+ *
36
+ * A hook is unauthenticated by construction, and until Core could check a signature itself, "verified"
37
+ * was a thing each handler asserted about itself -- so the rule that a request with no actor may not
38
+ * write had to hold for every hook, including the ones that had in fact proved their caller. Stating the
39
+ * check here moves it in front of the handler: Core verifies over the raw body, and a handler that runs
40
+ * has already been vouched for.
41
+ *
42
+ * `secret` names one of this Add-on's own declared secrets, so the value an operator set is never in the
43
+ * manifest. `deliveryIdHeader` names the header the provider identifies a delivery with; where it is
44
+ * given, Core refuses a delivery it has already accepted, which is what makes a webhook retried after a
45
+ * timeout harmless.
46
+ */
47
+ export type PhisAddonHookVerificationDescriptor = {
48
+ scheme: "hmac-sha256";
49
+ header: string;
50
+ /** What the header value is prefixed with, where it is -- GitHub sends `sha256=`. */
51
+ prefix?: string;
52
+ secret: string;
53
+ deliveryIdHeader?: string;
54
+ };
55
+ export type PhisAddonHookDescriptor = {
34
56
  id: string;
35
- method: PhiServerAddonHttpMethod;
57
+ method: PhisAddonHttpMethod;
36
58
  path: string;
37
59
  handler: string;
38
60
  bodyLimitBytes: number;
39
61
  rateLimitClass: string;
40
62
  timeoutMs: number;
41
63
  /**
42
- * Whether this hook is told which Site it was called for, through `x-phi-site-key`.
64
+ * How this hook's caller is checked, or absent where it cannot be.
65
+ *
66
+ * Absent is not a lesser variant of present: an unverified hook writes nothing at all -- not through
67
+ * `data:v1`, not by opening a thread -- because nothing distinguishes its caller from anybody who
68
+ * found the address. A hook that needs to write declares how it can be believed.
69
+ */
70
+ verification?: PhisAddonHookVerificationDescriptor;
71
+ /**
72
+ * Whether this hook is told which Site it was called for, through `x-phis-site-key`.
43
73
  *
44
74
  * A hook is unauthenticated by construction -- that is what makes it reachable by a payment provider
45
75
  * or by somebody else's Core. Without a Site it also has no capabilities at all, because every one of
@@ -50,7 +80,7 @@ export type PhiServerAddonHookDescriptor = {
50
80
  */
51
81
  siteScoped?: boolean;
52
82
  };
53
- export type PhiServerAddonCapabilityDescriptor = {
83
+ export type PhisAddonCapabilityDescriptor = {
54
84
  id: string;
55
85
  interfaceDigest: string;
56
86
  };
@@ -62,14 +92,14 @@ export type PhiServerAddonCapabilityDescriptor = {
62
92
  * reaches the implementation without a dynamic import. The interface digest is checked against what
63
93
  * this release offers, so an Add-on built for an older interface is refused rather than called.
64
94
  */
65
- export type PhiServerAddonServiceProviderDescriptor = {
66
- serviceKind: PhiServerServiceKind;
95
+ export type PhisAddonServiceProviderDescriptor = {
96
+ serviceKind: PhisServiceKind;
67
97
  providerKey: string;
68
98
  interfaceDigest: string;
69
99
  factoryExport: string;
70
- requiredCoreCapabilities: PhiServerCoreCapabilityId[];
100
+ requiredCoreCapabilities: PhisCoreCapabilityId[];
71
101
  };
72
- export type PhiServerAddonJobDescriptor = {
102
+ export type PhisAddonJobDescriptor = {
73
103
  id: string;
74
104
  handler: string;
75
105
  /**
@@ -88,7 +118,7 @@ export type PhiServerAddonJobDescriptor = {
88
118
  * check a value against its type before it is stored -- the same bargain as the schema and query
89
119
  * descriptors, one step smaller.
90
120
  */
91
- export type PhiServerAddonSettingDescriptor = {
121
+ export type PhisAddonSettingDescriptor = {
92
122
  name: string;
93
123
  type: "text" | "integer" | "boolean";
94
124
  /** Shown by the CLI when it lists what an Add-on can be configured with. */
@@ -107,8 +137,8 @@ export type PhiServerAddonSettingDescriptor = {
107
137
  * `siteAccess` names one of the Site's own claims; `role` names another role of this same Add-on. A
108
138
  * Site Admin may always grant any role of any Add-on, so a graph that reaches nobody still has a way in.
109
139
  */
110
- export type PhiServerAddonRoleGrantPolicy = {
111
- siteAccess: PhiServerSiteAccess;
140
+ export type PhisAddonRoleGrantPolicy = {
141
+ siteAccess: PhisSiteAccess;
112
142
  } | {
113
143
  role: string;
114
144
  };
@@ -122,23 +152,71 @@ export type PhiServerAddonRoleGrantPolicy = {
122
152
  * Core never interprets the name. It stores assignments, answers `has()`, and enforces `grantableBy`;
123
153
  * what `reviewer` means is the Add-on's business and stays there.
124
154
  */
125
- export type PhiServerAddonRoleDescriptor = {
155
+ export type PhisAddonGroupWriterDescriptor = {
156
+ /**
157
+ * Who may cause this Add-on to create a group or change who is in one.
158
+ *
159
+ * The same policy shape a role's `grantableBy` uses, evaluated the same way, and with the same floor:
160
+ * a Site Admin always may, so a badly declared condition never locks a Site out of its own groups.
161
+ */
162
+ allowedBy: PhisAddonRoleGrantPolicy;
163
+ };
164
+ export type PhisAddonRoleDescriptor = {
165
+ name: string;
166
+ description?: string;
167
+ grantableBy: PhisAddonRoleGrantPolicy;
168
+ };
169
+ /**
170
+ * A group this Add-on needs on every Site that runs it.
171
+ *
172
+ * Groups are Core's vocabulary about shared rows, so an Add-on cannot invent one at runtime and cannot
173
+ * put anybody in it -- the groups capability reads and grants nothing. What it can do is say which ones
174
+ * it needs, and Core creates them per Site when the Site asks for the Add-on. Who is in them stays with
175
+ * whoever administers the Site, which is the point: a package should not be able to hand out standing.
176
+ *
177
+ * The group carries this Add-on's id as its provider, and Core already filters group claims against the
178
+ * available providers -- so an Add-on's group stops granting anything the moment the Add-on is gone,
179
+ * without anybody having to remember to clean up.
180
+ */
181
+ export type PhisAddonGroupDescriptor = {
182
+ /** Unique within this Add-on. Frozen once a Site has one: memberships point at it by key. */
183
+ key: string;
184
+ /** What an administrator sees in the Site's group list. */
126
185
  name: string;
127
186
  description?: string;
128
- grantableBy: PhiServerAddonRoleGrantPolicy;
129
187
  };
130
- export type PhiServerAddonManifestV1 = {
131
- manifestVersion: typeof PHI_SERVER_ADDON_MANIFEST_VERSION;
188
+ /**
189
+ * One event this Add-on wants to be told about.
190
+ *
191
+ * Declared like a route: Core reaches the handler by the name the runtime exports it under, so nothing
192
+ * registers itself and what an Add-on listens to is readable before it runs. Several handlers may name
193
+ * the same event; each is delivered separately and retried separately.
194
+ */
195
+ export type PhisAddonEventHandlerDescriptor = {
196
+ id: string;
197
+ event: PhisAddonEventId;
198
+ handler: string;
199
+ };
200
+ export type PhisAddonManifestV1 = {
201
+ manifestVersion: typeof PHIS_ADDON_MANIFEST_VERSION;
132
202
  addonId: string;
133
203
  packageName: string;
134
204
  packageVersion: string;
135
205
  serverAbi: string;
136
- capabilities: PhiServerAddonCapabilityDescriptor[];
137
- serviceProviders: PhiServerAddonServiceProviderDescriptor[];
138
- requiredCoreCapabilities: PhiServerCoreCapabilityId[];
139
- apiRoutes: PhiServerAddonApiRouteDescriptor[];
140
- hooks: PhiServerAddonHookDescriptor[];
141
- jobs: PhiServerAddonJobDescriptor[];
206
+ capabilities: PhisAddonCapabilityDescriptor[];
207
+ serviceProviders: PhisAddonServiceProviderDescriptor[];
208
+ requiredCoreCapabilities: PhisCoreCapabilityId[];
209
+ apiRoutes: PhisAddonApiRouteDescriptor[];
210
+ hooks: PhisAddonHookDescriptor[];
211
+ jobs: PhisAddonJobDescriptor[];
212
+ /**
213
+ * The events this Add-on wants delivered. Absent means none.
214
+ *
215
+ * Optional like every field added after this manifest version was in the wild. Declaring one is not
216
+ * receiving it: an event reaches this Add-on only for a thread it holds a synced link to, so an
217
+ * Add-on that declares them and links nothing is told nothing.
218
+ */
219
+ eventHandlers?: PhisAddonEventHandlerDescriptor[];
142
220
  /**
143
221
  * What an operator may configure. Absent means nothing is.
144
222
  *
@@ -148,7 +226,7 @@ export type PhiServerAddonManifestV1 = {
148
226
  * fields may demand an explicit `null`, because no stored manifest predates them; later ones must
149
227
  * define what absence means and accept it.
150
228
  */
151
- settings?: PhiServerAddonSettingDescriptor[];
229
+ settings?: PhisAddonSettingDescriptor[];
152
230
  /**
153
231
  * The roles this Add-on defines. Absent means it defines none.
154
232
  *
@@ -156,14 +234,50 @@ export type PhiServerAddonManifestV1 = {
156
234
  * forgotten it. A name is frozen from the first assignment -- renaming one orphans every grant that
157
235
  * points at it, silently, because the grant is stored as text and text does not follow a rename.
158
236
  */
159
- roles?: PhiServerAddonRoleDescriptor[];
237
+ roles?: PhisAddonRoleDescriptor[];
238
+ /**
239
+ * The groups this Add-on needs. Absent means none.
240
+ *
241
+ * Optional like every field added after the manifest version was in the wild, and absent means the
242
+ * Add-on asks for no group rather than that it forgot to say so.
243
+ */
244
+ groups?: PhisAddonGroupDescriptor[];
245
+ /**
246
+ * Whether this Add-on may create groups of its own while it runs, and who may cause it to.
247
+ *
248
+ * The declared list above is for the groups a Site always has -- one per Add-on, known when the
249
+ * manifest was written. This is for the ones a Site produces as it runs: one per vendor, per team, per
250
+ * tenant, whose number and names nobody knew in advance.
251
+ *
252
+ * It stays a declaration rather than a capability an Add-on simply has, for the reason `grantableBy`
253
+ * exists: the manifest is covered by a digest the running code cannot rewrite, so the author states
254
+ * who may cause a group, and Core decides whether the condition is met. Without it, "an Add-on may
255
+ * make groups" would mean "an Add-on may make itself an authority container and fill it".
256
+ *
257
+ * What it may do is bounded by ownership either way: a group it creates carries its own id as the
258
+ * provider, and every write here refuses a group carrying anybody else's.
259
+ */
260
+ groupWriter?: PhisAddonGroupWriterDescriptor;
261
+ /**
262
+ * The Modules this package's other half contributes. Absent means it has no Module half.
263
+ *
264
+ * Each id is `<package name>/<module key>`, and the package name is this manifest's own -- one package
265
+ * carries one product, so there is nothing to resolve. What the field adds is the one thing Core
266
+ * cannot see from an artifact: whether a Module half exists at all. An Add-on that is only an Add-on,
267
+ * like a storage provider, says nothing here and is complete on its own.
268
+ *
269
+ * It is what lets enabling an Add-on for a Site check that the Site actually has the other half. A
270
+ * Site with the Add-on and not the Module has routes with no surface: nothing is broken, but an
271
+ * operator who thinks they switched a feature on finds nothing.
272
+ */
273
+ modules?: string[];
160
274
  /**
161
275
  * The tables this Add-on owns, or null when it owns none.
162
276
  *
163
277
  * There is no list of migration steps beside it. The descriptor is the desired shape; what has been
164
278
  * applied is recorded in the database, and Core derives the difference.
165
279
  */
166
- schema: PhiServerAddonSchemaDescriptor | null;
280
+ schema: PhisAddonSchemaDescriptor | null;
167
281
  /**
168
282
  * The statements this Add-on may run against its own tables, or null when it runs none.
169
283
  *
@@ -172,14 +286,14 @@ export type PhiServerAddonManifestV1 = {
172
286
  *
173
287
  * Optional for the reason `settings` is: it arrived after the manifest version was in the wild.
174
288
  */
175
- queries?: PhiServerAddonQueryCatalog | null;
289
+ queries?: PhisAddonQueryCatalog | null;
176
290
  /** The version of `schema`, or 0 when there is none. */
177
291
  migrationVersion: number;
178
292
  };
179
- export type PhiServerAddonRequestContext = {
293
+ export type PhisAddonRequestContext = {
180
294
  addonId: string;
181
295
  requestId: string;
182
- method: PhiServerAddonHttpMethod;
296
+ method: PhisAddonHttpMethod;
183
297
  url: string;
184
298
  headers: Headers;
185
299
  body: Uint8Array;
@@ -188,6 +302,15 @@ export type PhiServerAddonRequestContext = {
188
302
  site: {
189
303
  id: number;
190
304
  key: string;
305
+ /**
306
+ * Where this Site answers from, as the outside world reaches it.
307
+ *
308
+ * Core's own addresses are Site-relative, which is right for a page of that Site and useless in
309
+ * anything that leaves it: a listing another Core fetches, a link in an email, a picture in a
310
+ * feed. A handler cannot derive this -- a request carries the host it happened to arrive on, and
311
+ * an internal one carries none at all -- so it is the Site's own record that says it.
312
+ */
313
+ publicBaseUrl: string;
191
314
  } | null;
192
315
  actor: {
193
316
  kind: "operator";
@@ -205,10 +328,10 @@ export type PhiServerAddonRequestContext = {
205
328
  * Only what the manifest declared arrives. A capability is not fetched, looked up, or constructed by
206
329
  * the Add-on -- it is here or it is not, and it was already decided before the handler ran.
207
330
  */
208
- capabilities: PhiServerAddonCapabilities;
331
+ capabilities: PhisAddonCapabilities;
209
332
  signal: AbortSignal;
210
333
  };
211
- export type PhiServerAddonHandler = (context: PhiServerAddonRequestContext) => Promise<Response> | Response;
334
+ export type PhisAddonHandler = (context: PhisAddonRequestContext) => Promise<Response> | Response;
212
335
  /**
213
336
  * A service factory, as the runtime exports it.
214
337
  *
@@ -216,24 +339,25 @@ export type PhiServerAddonHandler = (context: PhiServerAddonRequestContext) => P
216
339
  * what makes that agreement checkable. The factory receives schema-validated profile configuration and
217
340
  * a restricted context, never a database handle or a general secret accessor.
218
341
  */
219
- export type PhiServerAddonServiceFactory = (config: unknown, context: unknown) => unknown;
220
- export type PhiServerAddonRuntimeV1 = {
221
- manifest: PhiServerAddonManifestV1;
222
- apiHandlers?: Readonly<Record<string, PhiServerAddonHandler>>;
223
- hookHandlers?: Readonly<Record<string, PhiServerAddonHandler>>;
224
- serviceFactories?: Readonly<Record<string, PhiServerAddonServiceFactory>>;
225
- jobHandlers?: Readonly<Record<string, PhiServerAddonJobHandler>>;
342
+ export type PhisAddonServiceFactory = (config: unknown, context: unknown) => unknown;
343
+ export type PhisAddonRuntimeV1 = {
344
+ manifest: PhisAddonManifestV1;
345
+ apiHandlers?: Readonly<Record<string, PhisAddonHandler>>;
346
+ hookHandlers?: Readonly<Record<string, PhisAddonHandler>>;
347
+ serviceFactories?: Readonly<Record<string, PhisAddonServiceFactory>>;
348
+ jobHandlers?: Readonly<Record<string, PhisAddonJobHandler>>;
349
+ eventHandlers?: Readonly<Record<string, PhisAddonEventHandler>>;
226
350
  };
227
- export type PhiServerAddonRuntimeModuleV1 = {
228
- phiServerAddon: PhiServerAddonRuntimeV1;
351
+ export type PhisAddonRuntimeModuleV1 = {
352
+ phisAddon: PhisAddonRuntimeV1;
229
353
  };
230
- export type PhiServerAddonBuildEntry = {
354
+ export type PhisAddonBuildEntry = {
231
355
  enabled: boolean;
232
356
  manifestDigest: string;
233
- manifest: PhiServerAddonManifestV1;
234
- load: () => Promise<PhiServerAddonRuntimeModuleV1>;
357
+ manifest: PhisAddonManifestV1;
358
+ load: () => Promise<PhisAddonRuntimeModuleV1>;
235
359
  };
236
- export type PhiServerAddonBuildManifest = Readonly<Record<string, PhiServerAddonBuildEntry>>;
360
+ export type PhisAddonBuildManifest = Readonly<Record<string, PhisAddonBuildEntry>>;
237
361
  /**
238
362
  * One installed Add-on, as the operator's desired state records it.
239
363
  *
@@ -242,7 +366,7 @@ export type PhiServerAddonBuildManifest = Readonly<Record<string, PhiServerAddon
242
366
  * digests bind the three apart: `manifestDigest` covers the descriptors below, `artifactDigest` covers
243
367
  * the file in the install root, and the loader refuses a file that no longer matches.
244
368
  */
245
- export type PhiServerAddonDesiredStateEntry = {
369
+ export type PhisAddonDesiredStateEntry = {
246
370
  addonId: string;
247
371
  packageName: string;
248
372
  packageVersion: string;
@@ -250,10 +374,10 @@ export type PhiServerAddonDesiredStateEntry = {
250
374
  /** SHA-256 of the installed artifact, and the cache key its load address carries. */
251
375
  artifactDigest: string;
252
376
  /** The validated descriptors, as read from the artifact at installation. */
253
- manifest: PhiServerAddonManifestV1;
377
+ manifest: PhisAddonManifestV1;
254
378
  enabled: boolean;
255
379
  };
256
- export type PhiServerAddonDesiredState = {
380
+ export type PhisAddonDesiredState = {
257
381
  formatVersion: 1;
258
- addons: PhiServerAddonDesiredStateEntry[];
382
+ addons: PhisAddonDesiredStateEntry[];
259
383
  };
package/dist/addon.js CHANGED
@@ -11,5 +11,5 @@
11
11
  * no node builtins -- the Add-on states, and Core checks. If something here ever does more than hand
12
12
  * back a constant, it is in the wrong package.
13
13
  */
14
- export const PHI_SERVER_ADDON_MANIFEST_VERSION = 1;
15
- export const PHI_SERVER_ADDON_ABI_VERSION = 1;
14
+ export const PHIS_ADDON_MANIFEST_VERSION = 1;
15
+ export const PHIS_ADDON_ABI_VERSION = 1;
@@ -0,0 +1,165 @@
1
+ import type { PhiMediaUploadPlan } from "./media-storage.js";
2
+ /**
3
+ * Files on a row: `@phis/server/assets:v1`.
4
+ *
5
+ * The slots are declared in the schema descriptor -- a name, a cardinality, a size ceiling, the media
6
+ * types it takes, who may fetch it -- and this is how they are filled. An Add-on says which row and
7
+ * which slot; where the object goes, what it is called and whether it arrived are Core's answers.
8
+ *
9
+ * There is one upload lifecycle in Core and this is not a second one. Core reserves, receives, measures
10
+ * and hashes without knowing who asked; the purpose stated here is read back at the last step, where
11
+ * the finished object becomes a row in the Add-on's table.
12
+ *
13
+ * **No method takes an object key and none returns one.** A key an Add-on chooses is a key Core cannot
14
+ * recognise later, and an object Core cannot recognise is one it can neither deliver nor account for.
15
+ * What comes back instead is a Media Asset id, which the delivery Core already has takes.
16
+ */
17
+ export type PhisAddonAssetView = {
18
+ id: string;
19
+ slot: string;
20
+ ownerId: string;
21
+ /** The file itself, as Core records it. */
22
+ mediaAssetId: number;
23
+ /**
24
+ * Where the file is served from, Site-relative, as Core addresses its own Media.
25
+ *
26
+ * Given rather than left to be composed, because composing it means knowing two things an Add-on has
27
+ * no way to be told: the route, and the cache revision it carries. A path built without the revision
28
+ * still delivers the file and never gets an immutable cache -- every viewer revalidates every time,
29
+ * and nothing about the result looks wrong enough to investigate.
30
+ *
31
+ * Read at the moment it is used, never stored. The revision rotates whenever the file's delivery
32
+ * changes, and a path kept from an earlier read would then be the stale one it exists to retire.
33
+ *
34
+ * What the path answers with is the slot's `delivery`: `public` to anyone, `authenticated` to a
35
+ * session, `internal` only to Core's own caller. The address is not the permission.
36
+ */
37
+ contentPath: string;
38
+ byteSize: number;
39
+ contentType: string;
40
+ /**
41
+ * The digest that was established, with the algorithm that produced it -- or null where none was.
42
+ *
43
+ * Established means one of two things: the endpoint verified what the client stated and would have
44
+ * refused anything else, or Core received the body and hashed it in flight. Null is a truthful "not
45
+ * established" -- an `etag` is an HTTP header and not a content hash, and a wrong digest here would
46
+ * be worse than none, because it invites a comparison.
47
+ *
48
+ * The algorithm is part of the answer and not an assumption. Which one an endpoint can attest to
49
+ * differs between Providers, so a bare value would be a number whose meaning the reader has to guess.
50
+ */
51
+ checksum: {
52
+ algorithm: string;
53
+ value: string;
54
+ } | null;
55
+ createdAt: string;
56
+ };
57
+ /**
58
+ * A place made for a file, and how to deliver it.
59
+ *
60
+ * The plan is the Provider's answer, not Core's: an endpoint that can take a body directly says so, and
61
+ * one that cannot names a Core route to stream through. An Add-on passes it to its client unread.
62
+ */
63
+ export type PhisAddonAssetReservationView = {
64
+ /** What the transfer is carried out against, and what `finalize` is called with. */
65
+ token: string;
66
+ plan: PhiMediaUploadPlan;
67
+ expiresAt: string;
68
+ };
69
+ export type PhisAssetsCapabilityV1 = {
70
+ /**
71
+ * Reserves a place for a file, before any of it is sent.
72
+ *
73
+ * The size is stated now and held to later, and the space is charged now rather than at the end. A
74
+ * ceiling decided at the end would bound the bookkeeping and not the store: ten clients could each
75
+ * write a gigabyte and have five of them refused afterwards, with the bytes already paid for.
76
+ *
77
+ * `sha256` is the client's statement of what it is about to send. Where the Storage Profile was found
78
+ * to verify one, it is signed into the plan and the endpoint refuses anything else -- so a wrong
79
+ * digest never becomes an object, and Core can record the figure as true without having seen a byte.
80
+ * Where it was not, the body comes through Core and Core hashes it instead.
81
+ *
82
+ * Refused where the slot is not declared, where the type is not one it takes, where the size is over
83
+ * the slot's ceiling or the Site's, where the Space is full, and where the acting user may not change
84
+ * the row. Attaching a file to a row changes it, so it is the row's authority that decides.
85
+ */
86
+ begin(input: {
87
+ table: string;
88
+ ownerId: string | number;
89
+ slot: string;
90
+ contentType: string;
91
+ sizeBytes: number;
92
+ sha256?: string;
93
+ /** What the file was called where it came from. Core makes one up when there is nothing to use. */
94
+ filename?: string;
95
+ }): Promise<PhisAddonAssetReservationView>;
96
+ /**
97
+ * Settles a reservation once the bytes are there, and makes the file a row.
98
+ *
99
+ * Core looks at what actually landed rather than believing the report: nothing there is not a file,
100
+ * and a different size than was reserved is not a file either. On a single-valued slot the file being
101
+ * replaced goes in the same transaction -- there is no moment in which the row holds two.
102
+ *
103
+ * Repeating it is safe. A client whose answer was lost cannot tell a finished upload from a failed
104
+ * one, so a second call answers with the same file rather than with an error.
105
+ */
106
+ finalize(input: {
107
+ table: string;
108
+ token: string;
109
+ /** Whatever the plan's issuer asked the client to bring back. Core passes it through unread. */
110
+ completion?: unknown;
111
+ }): Promise<PhisAddonAssetView>;
112
+ /** Gives an unredeemed reservation up. An abandoned one expires on its own; this is the tidy path. */
113
+ abandon(input: {
114
+ table: string;
115
+ token: string;
116
+ }): Promise<boolean>;
117
+ /**
118
+ * The files of a row. A reservation in flight is not among them, because it is not a file yet.
119
+ *
120
+ * Refused where the acting user may not read the row. Asking what a row carries is reading the row,
121
+ * and a slot's `delivery` does not answer it: a file whose id, name and size come back to anyone has
122
+ * already given up most of what `internal` was keeping.
123
+ */
124
+ list(input: {
125
+ table: string;
126
+ ownerId: string | number;
127
+ slot?: string;
128
+ }): Promise<PhisAddonAssetView[]>;
129
+ /**
130
+ * Takes a file out of its slot, and the file with it.
131
+ *
132
+ * Refused where the acting user may not change the row, which is the same authority `begin` is
133
+ * measured against -- taking a file away changes the row exactly as putting one there does. A file
134
+ * that is not there answers `false` rather than refusing, because a refusal would say it exists.
135
+ */
136
+ remove(input: {
137
+ table: string;
138
+ id: string;
139
+ }): Promise<boolean>;
140
+ /**
141
+ * What this Add-on's files take up on this Site, and what they are allowed to.
142
+ *
143
+ * Here for the reason `storage:v1` has the same call: the ceiling is the operator's and Core enforces
144
+ * it whether or not anybody read this first, so this exists to let an Add-on say *why*. A refusal an
145
+ * interface can explain before a person picks a file beats one that arrives after they waited for the
146
+ * upload, and a vendor who can see they are at their limit does not report it as a fault.
147
+ *
148
+ * The two figures are separate because both are spent. `bytes` is what is stored; `reservedBytes` is
149
+ * held for uploads in flight and is as unavailable as stored bytes until they finish or lapse. Room
150
+ * for one more file is `quotaBytes - (bytes + reservedBytes)`, which is the comparison `begin` makes.
151
+ *
152
+ * `quotaBytes` is the effective ceiling after inheritance -- the Space's own override where there is
153
+ * one, the Site's default for Add-on Spaces otherwise -- so it is the figure that will actually be
154
+ * applied and not a raw column. `null` means no ceiling. An explicit `0` is a revocation, not an
155
+ * absence.
156
+ *
157
+ * No object count, unlike `storage:v1`. Core keeps a running total of bytes and none of files, and
158
+ * counting them on demand would be the unbounded read the ceiling exists to prevent.
159
+ */
160
+ usage(): Promise<{
161
+ bytes: number;
162
+ reservedBytes: number;
163
+ quotaBytes: number | null;
164
+ }>;
165
+ };
package/dist/assets.js ADDED
@@ -0,0 +1 @@
1
+ export {};