@foir/sdk 0.10.0 → 0.12.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.
- package/CHANGELOG.md +353 -2
- package/README.md +10 -2
- package/dist/app/client.d.ts +371 -0
- package/dist/app/contract/index.d.ts +23 -0
- package/dist/app/contract/proto/apps/v1/apps_pb.d.ts +1465 -0
- package/dist/app/contract/proto/apps/v1/discovery_pb.d.ts +678 -0
- package/dist/app/dispatch.d.ts +64 -0
- package/dist/app/errors.d.ts +50 -0
- package/dist/app/index.d.ts +40 -0
- package/dist/app/manifest.d.ts +56 -0
- package/dist/app/naming.d.ts +31 -0
- package/dist/app/record-key.d.ts +17 -0
- package/dist/app/ticket.d.ts +37 -0
- package/dist/app/vocabulary.generated.d.ts +20 -0
- package/dist/app-contract.js +124 -0
- package/dist/app.js +998 -0
- package/dist/auth/auth.d.ts +24 -6
- package/dist/auth/index.d.ts +2 -1
- package/dist/auth/ops.d.ts +8 -0
- package/dist/auth.js +230 -60
- package/dist/client.js +80 -15
- package/dist/codegen/preset.d.ts +3 -2
- package/dist/data/builtins.d.ts +17 -1
- package/dist/data/client.d.ts +10 -8
- package/dist/data/document.d.ts +13 -0
- package/dist/data/index.d.ts +15 -5
- package/dist/data/manifest.d.ts +31 -3
- package/dist/data/schema.d.ts +16 -9
- package/dist/data/selection.d.ts +15 -0
- package/dist/data/transport.d.ts +277 -23
- package/dist/data/ws-transport.d.ts +8 -2
- package/dist/data.js +313 -72
- package/dist/graphql/client.d.ts +1 -1
- package/dist/graphql/foir-request.d.ts +1 -1
- package/dist/index.js +0 -9
- package/dist/live/drift.d.ts +9 -10
- package/dist/live.js +1 -1
- package/dist/next.js +158 -37
- package/dist/placement/index.d.ts +129 -0
- package/dist/placement.js +144 -0
- package/dist/push.js +212 -50
- package/dist/react.js +158 -37
- package/dist/server/callback-client.d.ts +30 -9
- package/dist/server/continue-run.d.ts +56 -0
- package/dist/server/index.d.ts +6 -3
- package/dist/server/operation-types.d.ts +114 -65
- package/dist/server/read-dispatch.d.ts +53 -0
- package/dist/server/require-scoped-token.d.ts +71 -7
- package/dist/server/verify-scoped-token.d.ts +109 -3
- package/dist/server.js +566 -138
- package/dist/transport/foir-http.d.ts +103 -5
- package/package.json +24 -6
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client an installed app calls Foir with.
|
|
3
|
+
*
|
|
4
|
+
* An app is installed in many projects, and each install maps the app's
|
|
5
|
+
* sources onto whichever models that project's operator chose. So an app has
|
|
6
|
+
* no schema to generate a client from: it learns the model keys at request
|
|
7
|
+
* time, from the install's own configuration, and names them in each call.
|
|
8
|
+
* `createFoirClient` is the client for an endpoint that knows its project;
|
|
9
|
+
* this is the one for an endpoint that does not.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const { dispatch } = await requireScopedToken(request, { verifier });
|
|
13
|
+
* const foir = createAppClient({ apiUrl, dispatch });
|
|
14
|
+
* const config = await foir.install.config();
|
|
15
|
+
* const products = config.sources.find((source) => source.key === 'product');
|
|
16
|
+
* await foir.records.upsert({ model: products.model, naturalKey: handle, data });
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* The credential is the token the dispatch carried, and nothing else. Every
|
|
20
|
+
* call goes to the data API as that token, so what it may do is exactly what
|
|
21
|
+
* the operation declared. NOTHING IS CHECKED HERE FIRST: the client does not
|
|
22
|
+
* read the token, and a call the token cannot make is refused by the platform,
|
|
23
|
+
* whose answer is the error thrown (see `./errors`).
|
|
24
|
+
*
|
|
25
|
+
* A client is cheap and holds no state. Build one per dispatch and let it go
|
|
26
|
+
* with the request, so a credential revoked in Foir stops working on the next
|
|
27
|
+
* call rather than whenever a cache expires.
|
|
28
|
+
*/
|
|
29
|
+
import type { FoirDispatchCredential } from '../data/transport';
|
|
30
|
+
import { type FoirRetryPolicy } from '../transport/retry';
|
|
31
|
+
type Json = null | boolean | number | string | Json[] | {
|
|
32
|
+
[key: string]: Json;
|
|
33
|
+
};
|
|
34
|
+
type JsonObject = {
|
|
35
|
+
[key: string]: Json;
|
|
36
|
+
};
|
|
37
|
+
export interface AppClientConfig {
|
|
38
|
+
/** The data API's origin, e.g. `https://api.foir.dev`. */
|
|
39
|
+
apiUrl: string;
|
|
40
|
+
/**
|
|
41
|
+
* The token to call as: `dispatch` from `requireScopedToken`, the token an
|
|
42
|
+
* exchange ticket was redeemed for (`redeemExchangeTicket`), or an ASYNC
|
|
43
|
+
* run's callback token (`{ token: () => payload.callback.token }`), which
|
|
44
|
+
* keeps writing records after the dispatch token has expired.
|
|
45
|
+
*/
|
|
46
|
+
dispatch: FoirDispatchCredential;
|
|
47
|
+
/** Defaults to the global `fetch`. */
|
|
48
|
+
fetch?: typeof globalThis.fetch;
|
|
49
|
+
/** Extra init for every request, e.g. an abort signal. */
|
|
50
|
+
requestInit?: Omit<RequestInit, 'method' | 'headers' | 'body'>;
|
|
51
|
+
/**
|
|
52
|
+
* How a call that got no answer, a 5xx or a 429 is tried again. Three
|
|
53
|
+
* attempts unless told otherwise; `{ attempts: 1 }` turns it off. Every call
|
|
54
|
+
* here is safe to repeat except a notification sent without an
|
|
55
|
+
* `idempotencyKey`, which a retry after a lost answer can deliver twice.
|
|
56
|
+
*/
|
|
57
|
+
retry?: FoirRetryPolicy;
|
|
58
|
+
}
|
|
59
|
+
/** Which way one mapped field is allowed to move. */
|
|
60
|
+
export type AppSyncDirection = 'SOURCE_ONLY' | 'SINK_ONLY' | 'BIDIRECTIONAL';
|
|
61
|
+
export interface AppFieldMapping {
|
|
62
|
+
/** The app's own field name, from its manifest. */
|
|
63
|
+
appField: string;
|
|
64
|
+
/** The project model's field key. */
|
|
65
|
+
modelField: string;
|
|
66
|
+
/**
|
|
67
|
+
* SINK_ONLY means the operator has said Foir owns the field: a value from
|
|
68
|
+
* the app's side must not be written over it. An unrecognised direction
|
|
69
|
+
* reads as SINK_ONLY, which withholds the field rather than writing on a
|
|
70
|
+
* guess.
|
|
71
|
+
*/
|
|
72
|
+
direction: AppSyncDirection;
|
|
73
|
+
}
|
|
74
|
+
/** One of the operator's mappings: a source or sink of the app onto a model. */
|
|
75
|
+
export interface AppBinding {
|
|
76
|
+
/** The app's source or sink name. */
|
|
77
|
+
key: string;
|
|
78
|
+
/** The project model the operator mapped it onto. */
|
|
79
|
+
model: string;
|
|
80
|
+
/** The model field the app's records are keyed on. Empty when the mapping names none. */
|
|
81
|
+
naturalKey: string;
|
|
82
|
+
fields: AppFieldMapping[];
|
|
83
|
+
/** REFERENCE: Foir reads the source's fields live. SYNC: it holds a copy. A sink is a copy. */
|
|
84
|
+
mode: 'SYNC' | 'REFERENCE';
|
|
85
|
+
}
|
|
86
|
+
/** An install's own configuration, as the operator left it. */
|
|
87
|
+
export interface AppInstallConfig {
|
|
88
|
+
name: string;
|
|
89
|
+
enabled: boolean;
|
|
90
|
+
middlewareUrl: string;
|
|
91
|
+
targetModels: string[];
|
|
92
|
+
/** The address the app's vendor is told to deliver webhooks to. Empty when the app declares none. */
|
|
93
|
+
webhookUrl: string;
|
|
94
|
+
/** The operator's answers to the manifest's settings schema. */
|
|
95
|
+
settings: JsonObject;
|
|
96
|
+
sources: AppBinding[];
|
|
97
|
+
sinks: AppBinding[];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The status an install reports. Leaving out `subscriptionHealth`,
|
|
101
|
+
* `authStatus`, `deletionStrategy` or `extra` keeps the stored value; leaving
|
|
102
|
+
* out any of the `lastSync*` or `lastError*` fields clears it. So send the
|
|
103
|
+
* sync fields together or not at all.
|
|
104
|
+
*/
|
|
105
|
+
export interface AppStatusReport {
|
|
106
|
+
lastSyncAt?: string;
|
|
107
|
+
lastSyncDurationMs?: number;
|
|
108
|
+
lastSyncRecordCount?: number;
|
|
109
|
+
/** null clears the field. */
|
|
110
|
+
lastError?: string | null;
|
|
111
|
+
lastErrorAt?: string | null;
|
|
112
|
+
subscriptionHealth?: 'healthy' | 'degraded' | 'failed';
|
|
113
|
+
authStatus?: 'authenticated' | 'unverified' | 'stale' | 'expired';
|
|
114
|
+
deletionStrategy?: string;
|
|
115
|
+
/** Replaced whole on every report that carries it. */
|
|
116
|
+
extra?: JsonObject;
|
|
117
|
+
}
|
|
118
|
+
/** A record as a read by model key answers it. */
|
|
119
|
+
export interface AppRecord {
|
|
120
|
+
/** The platform's own id. What `records.delete` takes. */
|
|
121
|
+
id: string;
|
|
122
|
+
naturalKey: string | null;
|
|
123
|
+
/** The customer the record belongs to, or null. */
|
|
124
|
+
ownerId: string | null;
|
|
125
|
+
/** The fields that were asked for and could be read, by model field key. */
|
|
126
|
+
fields: JsonObject;
|
|
127
|
+
/** Fields that were asked for and could not be selected, so the answer says nothing about them. */
|
|
128
|
+
unreadable: string[];
|
|
129
|
+
}
|
|
130
|
+
export interface AppUpsertResult {
|
|
131
|
+
/** The record as the platform holds it now. */
|
|
132
|
+
record: Record<string, unknown>;
|
|
133
|
+
/** The platform's own id for it, when the answer carried one. */
|
|
134
|
+
id: string | null;
|
|
135
|
+
created: boolean;
|
|
136
|
+
/** True when nothing was written because Foir already holds a newer copy (`sourceModifiedAt`). */
|
|
137
|
+
stale: boolean;
|
|
138
|
+
}
|
|
139
|
+
export type AppCustomerSaveResult = (AppUpsertResult & {
|
|
140
|
+
unlinked: false;
|
|
141
|
+
customerCreated: boolean;
|
|
142
|
+
}) | {
|
|
143
|
+
unlinked: true;
|
|
144
|
+
};
|
|
145
|
+
export interface AppCustomerEraseResult {
|
|
146
|
+
recordDeleted: boolean;
|
|
147
|
+
identityRemoved: boolean;
|
|
148
|
+
customerDeleted: boolean;
|
|
149
|
+
/** Another sign-in method, or something else they own; null when the customer went or none was found. */
|
|
150
|
+
customerKeptBecause: string | null;
|
|
151
|
+
}
|
|
152
|
+
export interface AppNotification {
|
|
153
|
+
customerId: string;
|
|
154
|
+
/** The in-app title, and the email's subject when `email` names none. */
|
|
155
|
+
title: string;
|
|
156
|
+
message?: string;
|
|
157
|
+
/** A notification category the project defines. */
|
|
158
|
+
category?: string;
|
|
159
|
+
actionUrl?: string;
|
|
160
|
+
imageUrl?: string;
|
|
161
|
+
/** The email, when the send should reach one. Without it, in-app and push only. */
|
|
162
|
+
email?: {
|
|
163
|
+
subject?: string;
|
|
164
|
+
body: string;
|
|
165
|
+
ctas?: ReadonlyArray<{
|
|
166
|
+
label: string;
|
|
167
|
+
url: string;
|
|
168
|
+
}>;
|
|
169
|
+
};
|
|
170
|
+
/** Makes the send safe to repeat: the same key from the same operation tells the customer once. */
|
|
171
|
+
idempotencyKey?: string;
|
|
172
|
+
}
|
|
173
|
+
export interface AppConsentChange {
|
|
174
|
+
customerId: string;
|
|
175
|
+
/** A notification category the project makes opt-in. */
|
|
176
|
+
category: string;
|
|
177
|
+
/** The channel the consent is for, as the project's preferences name it (for example `email`). */
|
|
178
|
+
channel: string;
|
|
179
|
+
enabled: boolean;
|
|
180
|
+
/**
|
|
181
|
+
* When the change was made at its source, as an RFC 3339 time. The platform
|
|
182
|
+
* keeps the latest it has taken and writes nothing for an older one,
|
|
183
|
+
* answering `stale`.
|
|
184
|
+
*/
|
|
185
|
+
sourceModifiedAt?: string;
|
|
186
|
+
}
|
|
187
|
+
/** What the calling token is, and what it is served. Read from the platform, never off the token. */
|
|
188
|
+
export interface AppWhoami {
|
|
189
|
+
/** The app the token acts for, when it acts for one. */
|
|
190
|
+
app: string | null;
|
|
191
|
+
/** The capabilities the token carries. */
|
|
192
|
+
capabilities: string[];
|
|
193
|
+
/** The models its record capabilities name, by verb. */
|
|
194
|
+
models: {
|
|
195
|
+
read: string[];
|
|
196
|
+
write: string[];
|
|
197
|
+
delete: string[];
|
|
198
|
+
publish: string[];
|
|
199
|
+
};
|
|
200
|
+
/** The scopes its calls are held to. */
|
|
201
|
+
effective: string[];
|
|
202
|
+
expiresAt: string | null;
|
|
203
|
+
}
|
|
204
|
+
export interface AppClient {
|
|
205
|
+
/**
|
|
206
|
+
* Run a hand-written document as the app and answer its whole `data`. For a
|
|
207
|
+
* field this client has no method for. A read asking for something the token
|
|
208
|
+
* is not served throws rather than answering the rest without it.
|
|
209
|
+
*/
|
|
210
|
+
request<T = Record<string, unknown>>(document: string, variables?: Record<string, unknown>): Promise<T>;
|
|
211
|
+
/**
|
|
212
|
+
* What the platform says this token is and may do. For an app that has to
|
|
213
|
+
* SHOW that; a call the token cannot make needs no check first, since the
|
|
214
|
+
* platform's refusal already says why.
|
|
215
|
+
*/
|
|
216
|
+
whoami(): Promise<AppWhoami>;
|
|
217
|
+
/** The install this token acts for: its configuration, its credential, its status. */
|
|
218
|
+
install: {
|
|
219
|
+
/** The operator's settings and mappings. Needs `config:read`. */
|
|
220
|
+
config(): Promise<AppInstallConfig>;
|
|
221
|
+
/**
|
|
222
|
+
* The install's credential, by the manifest's credential field names.
|
|
223
|
+
* Values are always strings. Needs `secrets:read.app`.
|
|
224
|
+
*/
|
|
225
|
+
credentials(): Promise<Record<string, string>>;
|
|
226
|
+
/**
|
|
227
|
+
* Write or rotate credential fields. An empty value is skipped, not
|
|
228
|
+
* cleared, and a call naming no non-empty field is refused. Needs
|
|
229
|
+
* `secrets:put.app`.
|
|
230
|
+
*/
|
|
231
|
+
writeCredentials(values: Record<string, string>): Promise<void>;
|
|
232
|
+
/** Report the install's health. Needs `status:write`. See {@link AppStatusReport}. */
|
|
233
|
+
reportStatus(status: AppStatusReport): Promise<void>;
|
|
234
|
+
};
|
|
235
|
+
/** Records of a model the install maps, named by its key. */
|
|
236
|
+
records: {
|
|
237
|
+
/**
|
|
238
|
+
* Write the record at a natural key, making it when there is none.
|
|
239
|
+
* `ownerId` names the customer it belongs to (needs
|
|
240
|
+
* `records:attribute:<model>`); `sourceModifiedAt` is when the object this
|
|
241
|
+
* mirrors last changed at its source, so an older copy arriving late is
|
|
242
|
+
* not written.
|
|
243
|
+
*/
|
|
244
|
+
upsert(input: {
|
|
245
|
+
model: string;
|
|
246
|
+
naturalKey: string;
|
|
247
|
+
data: JsonObject;
|
|
248
|
+
ownerId?: string;
|
|
249
|
+
sourceModifiedAt?: string;
|
|
250
|
+
}): Promise<AppUpsertResult>;
|
|
251
|
+
/**
|
|
252
|
+
* One record by natural key or by id, or null. It reads the PUBLISHED
|
|
253
|
+
* version: on a model with publishing, a record that was never published
|
|
254
|
+
* reads as null, the same as one that does not exist. A field that cannot
|
|
255
|
+
* be selected as written is reported in `unreadable` rather than failing
|
|
256
|
+
* the read.
|
|
257
|
+
*/
|
|
258
|
+
read(model: string, by: {
|
|
259
|
+
naturalKey: string;
|
|
260
|
+
} | {
|
|
261
|
+
id: string;
|
|
262
|
+
}, fields: readonly string[]): Promise<AppRecord | null>;
|
|
263
|
+
/** Delete one record by its platform id, which only a read supplies. */
|
|
264
|
+
delete(model: string, id: string): Promise<{
|
|
265
|
+
deleted: boolean;
|
|
266
|
+
}>;
|
|
267
|
+
/**
|
|
268
|
+
* Drop what the data API has cached for one record, by natural key or id,
|
|
269
|
+
* so its next read resolves it again. False when Foir holds no such
|
|
270
|
+
* record. For a source read live from its host.
|
|
271
|
+
*/
|
|
272
|
+
refresh(model: string, idOrKey: string): Promise<{
|
|
273
|
+
refreshed: boolean;
|
|
274
|
+
}>;
|
|
275
|
+
/**
|
|
276
|
+
* Change the natural key of the one record a filter matches. The platform
|
|
277
|
+
* refuses a filter matching none or several, and a key already held.
|
|
278
|
+
*/
|
|
279
|
+
rename(model: string, where: JsonObject, naturalKey: string): Promise<void>;
|
|
280
|
+
/**
|
|
281
|
+
* Update every record matching a filter, in one call. `where` and `data`
|
|
282
|
+
* are the model's typed filter and update, by model field key. An empty
|
|
283
|
+
* filter is refused: it would change every record.
|
|
284
|
+
*/
|
|
285
|
+
updateMany(model: string, input: {
|
|
286
|
+
where: JsonObject;
|
|
287
|
+
data: JsonObject;
|
|
288
|
+
idempotencyKey?: string;
|
|
289
|
+
}): Promise<{
|
|
290
|
+
count: number;
|
|
291
|
+
ids: string[];
|
|
292
|
+
}>;
|
|
293
|
+
/**
|
|
294
|
+
* How many records match, grouped by one field, counted by the platform.
|
|
295
|
+
* It groups only by an indexed field, largest group first; `truncated`
|
|
296
|
+
* says `limit` cut the list short.
|
|
297
|
+
*/
|
|
298
|
+
aggregate(model: string, query: {
|
|
299
|
+
groupBy: string;
|
|
300
|
+
where?: JsonObject;
|
|
301
|
+
limit?: number;
|
|
302
|
+
}): Promise<{
|
|
303
|
+
groups: Array<{
|
|
304
|
+
key: string | null;
|
|
305
|
+
count: number;
|
|
306
|
+
}>;
|
|
307
|
+
truncated: boolean;
|
|
308
|
+
}>;
|
|
309
|
+
/**
|
|
310
|
+
* The record of a customer the app names by identity: their id at the
|
|
311
|
+
* external system, which is also the subject of their sign-in with it. For
|
|
312
|
+
* a source that declares an identity. The platform finds the customer
|
|
313
|
+
* through the sign-in the install pairs with the source, at the install's
|
|
314
|
+
* level, and never by email.
|
|
315
|
+
*/
|
|
316
|
+
customer: {
|
|
317
|
+
/**
|
|
318
|
+
* Merge `data` into the customer's record, making the record when they
|
|
319
|
+
* have none, and the customer when `email` is given and nobody holds the
|
|
320
|
+
* identity. An identity nobody holds, with no email, is `{ unlinked:
|
|
321
|
+
* true }`: nothing was written, and they are known once they sign in.
|
|
322
|
+
*/
|
|
323
|
+
save(input: {
|
|
324
|
+
model: string;
|
|
325
|
+
identity: string;
|
|
326
|
+
data: JsonObject;
|
|
327
|
+
email?: string;
|
|
328
|
+
sourceModifiedAt?: string;
|
|
329
|
+
}): Promise<AppCustomerSaveResult>;
|
|
330
|
+
/** The customer's record, or null when nobody holds the identity or they hold no record. */
|
|
331
|
+
read(model: string, identity: string): Promise<AppRecord | null>;
|
|
332
|
+
/**
|
|
333
|
+
* Erase what the app holds for the customer, as a privacy request asks:
|
|
334
|
+
* their record and the identity's link, and the customer when nothing
|
|
335
|
+
* else is attached to them. Needs `customers:write` as well.
|
|
336
|
+
*/
|
|
337
|
+
erase(model: string, identity: string): Promise<AppCustomerEraseResult>;
|
|
338
|
+
/**
|
|
339
|
+
* The other direction: the identities a customer the app names by id
|
|
340
|
+
* signs in with there, most recently seen first. Empty when they sign in
|
|
341
|
+
* with none. For an app acting on an event that names the Foir customer.
|
|
342
|
+
*/
|
|
343
|
+
identities(model: string, customerId: string): Promise<string[]>;
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
customers: {
|
|
347
|
+
/**
|
|
348
|
+
* The customer who has this email at the install's level, made when there
|
|
349
|
+
* is none. The same customer every time for one email. Needs
|
|
350
|
+
* `customers:write`.
|
|
351
|
+
*/
|
|
352
|
+
ensure(email: string): Promise<{
|
|
353
|
+
id: string;
|
|
354
|
+
email: string;
|
|
355
|
+
}>;
|
|
356
|
+
};
|
|
357
|
+
notifications: {
|
|
358
|
+
/** Tell a customer something. Needs `notifications:write`. */
|
|
359
|
+
send(notification: AppNotification): Promise<void>;
|
|
360
|
+
/**
|
|
361
|
+
* Set a named customer's consent in an opt-in category. Needs
|
|
362
|
+
* `notifications:consent`, which only an operation's token carries.
|
|
363
|
+
*/
|
|
364
|
+
setConsent(change: AppConsentChange): Promise<{
|
|
365
|
+
stale: boolean;
|
|
366
|
+
}>;
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/** Build the client an installed app calls Foir with. See the module doc. */
|
|
370
|
+
export declare function createAppClient(config: AppClientConfig): AppClient;
|
|
371
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app contract, as the messages the platform itself reads and writes.
|
|
3
|
+
*
|
|
4
|
+
* A manifest is `AppConfig`, and a discovery answer is `DiscoveryResult`. They
|
|
5
|
+
* are generated from the same definitions the platform decodes them with, so a
|
|
6
|
+
* manifest built with `create(AppConfigSchema, …)` and written with `toJson`
|
|
7
|
+
* cannot carry a key the platform would refuse: it would not compile.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { create, toJson } from '@bufbuild/protobuf';
|
|
11
|
+
* import { AppConfigSchema } from '@foir/sdk/app/contract';
|
|
12
|
+
*
|
|
13
|
+
* const manifest = create(AppConfigSchema, { name: 'my-app', version: '1.0.0' });
|
|
14
|
+
* const served = toJson(AppConfigSchema, manifest, { useProtoFieldName: true });
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* These are protobuf-es messages, so this subpath needs `@bufbuild/protobuf`
|
|
18
|
+
* installed beside the SDK; the rest of the SDK does not. The messages follow
|
|
19
|
+
* the platform's contract, which only ever grows: a field or a message can
|
|
20
|
+
* appear in any release, and none is removed without a breaking one.
|
|
21
|
+
*/
|
|
22
|
+
export * from './proto/apps/v1/apps_pb.js';
|
|
23
|
+
export * from './proto/apps/v1/discovery_pb.js';
|