primitive-functions 0.2.0-alpha.0 → 0.2.0-alpha.2

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 (3) hide show
  1. package/index.d.ts +1013 -37
  2. package/index.js +57 -1
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -10,16 +10,34 @@
10
10
  * time, and reaching the stub means it did not.
11
11
  */
12
12
 
13
- /** The signed-in user on whose behalf the invocation runs. */
13
+ /**
14
+ * The signed-in user who invoked the function over HTTP.
15
+ *
16
+ * Attribution, never authority (#3279): function code acts as the system
17
+ * whoever called it, so this says WHO asked, and the rows the function writes
18
+ * are attributed to them. A trigger fire has no caller and `ctx.user` is
19
+ * null.
20
+ */
14
21
  export interface FunctionUser {
15
22
  userId: string;
16
23
  email: string | null;
17
24
  }
18
25
 
19
- /** What started this invocation. Only `http` exists today. */
20
- export interface FunctionTrigger {
21
- kind: "http";
22
- }
26
+ /** What started this invocation. */
27
+ export type FunctionTrigger =
28
+ | { kind: "http" }
29
+ | { kind: "webhook"; [key: string]: unknown }
30
+ | { kind: "cron"; [key: string]: unknown }
31
+ | { kind: "database"; databaseId: string; databaseType: string; [key: string]: unknown }
32
+ /**
33
+ * A DSL workflow's `workflow.call` step reached this function (#3288).
34
+ *
35
+ * TRANSITIONAL: the bridge exists so a workflow tree can migrate leaf-first
36
+ * during the transition, and it retires with the workflow engine in phase 7,
37
+ * taking this variant with it. `workflowKey` names the PARENT workflow, and
38
+ * `runId`/`stepId` locate the step inside its run.
39
+ */
40
+ | { kind: "workflow"; workflowKey: string; runId: string; stepId: string };
23
41
 
24
42
  /** The result shape of an operation that answers in response headers. */
25
43
  export interface HeaderResult {
@@ -39,18 +57,301 @@ export interface PrimitivePlatformError extends Error {
39
57
 
40
58
  /**
41
59
  * The typed HTTP profile, generated from the platform's OpenAPI document. Its
42
- * namespaces mirror the operation ids: databaseTypeConfigs, databases, documents, functions.
60
+ * namespaces mirror the operation ids: analytics, blobBuckets, channels, collections, configVars, connections, databaseTypeConfigs, databases, documents, email, functions, gemini, groups, integrations, iterations, llm, locks, notifications, prompts, resourceMetadata, secrets, users.
43
61
  * Methods take one options object carrying the operation's path parameters,
44
62
  * query parameters, request headers (under their wire names) and `body`.
45
63
  *
46
64
  * Generated from the same operations as the runtime profile, so a method that
47
65
  * type-checks is a method the platform publishes — and a missing path
48
66
  * parameter or a missing required header is a compile error rather than a
49
- * `TypeError` at invoke time. Namespaces the GATEWAY does not yet allow are
50
- * still declared: they exist, they answer the gateway's structured denial, and
51
- * hiding them would make a route's availability look like a typo.
67
+ * `TypeError` at invoke time. Every call runs with the app's own SYSTEM
68
+ * authority (#3279): the invocation gate was the authorization. The three
69
+ * namespaces the gateway does not admit (`functions`, `iterations`,
70
+ * `databaseTypeConfigs`) are still declared: they exist, they answer the
71
+ * gateway's structured denial, and hiding them would make a route's
72
+ * availability look like a typo.
52
73
  */
53
74
  export interface FunctionApi {
75
+ "analytics": {
76
+ /** `analytics.cohortRetention` — GET /analytics/cohort-retention. Cohort retention metrics */
77
+ "cohortRetention"(args?: {
78
+ "current_timestamp"?: string | number | boolean;
79
+ "_bust"?: string | number | boolean;
80
+ }): Promise<any>;
81
+ /** `analytics.dailyActive` — GET /analytics/daily-active. Daily active user time series */
82
+ "dailyActive"(args?: {
83
+ "windowDays"?: string | number | boolean;
84
+ "current_timestamp"?: string | number | boolean;
85
+ "_bust"?: string | number | boolean;
86
+ }): Promise<any>;
87
+ /** `analytics.errorGroups` — GET /analytics/errors/groups. Group failure events by fingerprint with per-day count buckets */
88
+ "errorGroups"(args?: {
89
+ "windowDays"?: string | number | boolean;
90
+ "limit"?: string | number | boolean;
91
+ "current_timestamp"?: string | number | boolean;
92
+ "_bust"?: string | number | boolean;
93
+ "filter"?: Record<string, Record<string, string | number | boolean | Array<string | number | boolean>>>;
94
+ }): Promise<any>;
95
+ /** `analytics.integrationUsage` — GET /analytics/integrations. Integration usage metrics */
96
+ "integrationUsage"(args?: {
97
+ "windowDays"?: string | number | boolean;
98
+ "current_timestamp"?: string | number | boolean;
99
+ "_bust"?: string | number | boolean;
100
+ }): Promise<any>;
101
+ /** `analytics.listEvents` — GET /analytics/events. List analytics events */
102
+ "listEvents"(args?: {
103
+ "windowDays"?: string | number | boolean;
104
+ "page"?: string | number | boolean;
105
+ "current_timestamp"?: string | number | boolean;
106
+ "_bust"?: string | number | boolean;
107
+ "filter"?: Record<string, Record<string, string | number | boolean | Array<string | number | boolean>>>;
108
+ }): Promise<any>;
109
+ /** `analytics.listEventsGrouped` — GET /analytics/events/grouped. List analytics events grouped by dimension */
110
+ "listEventsGrouped"(args?: {
111
+ "windowDays"?: string | number | boolean;
112
+ "groupBy"?: string | number | boolean;
113
+ "current_timestamp"?: string | number | boolean;
114
+ "_bust"?: string | number | boolean;
115
+ "filter"?: Record<string, Record<string, string | number | boolean | Array<string | number | boolean>>>;
116
+ }): Promise<any>;
117
+ /** `analytics.overviewDau` — GET /analytics/overview/dau. Daily active users overview */
118
+ "overviewDau"(args?: {
119
+ "current_timestamp"?: string | number | boolean;
120
+ "_bust"?: string | number | boolean;
121
+ }): Promise<any>;
122
+ /** `analytics.overviewGrowth` — GET /analytics/overview/growth. User growth overview */
123
+ "overviewGrowth"(args?: {
124
+ "windowDays"?: string | number | boolean;
125
+ "current_timestamp"?: string | number | boolean;
126
+ "_bust"?: string | number | boolean;
127
+ }): Promise<any>;
128
+ /** `analytics.overviewMau` — GET /analytics/overview/mau. Monthly active users overview */
129
+ "overviewMau"(args?: {
130
+ "current_timestamp"?: string | number | boolean;
131
+ "_bust"?: string | number | boolean;
132
+ }): Promise<any>;
133
+ /** `analytics.overviewWau` — GET /analytics/overview/wau. Weekly active users overview */
134
+ "overviewWau"(args?: {
135
+ "current_timestamp"?: string | number | boolean;
136
+ "_bust"?: string | number | boolean;
137
+ }): Promise<any>;
138
+ /** `analytics.rollingActive` — GET /analytics/rolling-active. Rolling active user time series */
139
+ "rollingActive"(args?: {
140
+ "windowDays"?: string | number | boolean;
141
+ "current_timestamp"?: string | number | boolean;
142
+ "_bust"?: string | number | boolean;
143
+ }): Promise<any>;
144
+ /** `analytics.searchUsers` — GET /analytics/users/search. Search app users for analytics */
145
+ "searchUsers"(args?: {
146
+ "q"?: string | number | boolean;
147
+ "limit"?: string | number | boolean;
148
+ "current_timestamp"?: string | number | boolean;
149
+ "_bust"?: string | number | boolean;
150
+ }): Promise<any>;
151
+ /** `analytics.topPrompts` — GET /analytics/prompts/top. List the most-executed prompts */
152
+ "topPrompts"(args?: {
153
+ "windowDays"?: string | number | boolean;
154
+ "limit"?: string | number | boolean;
155
+ "current_timestamp"?: string | number | boolean;
156
+ "_bust"?: string | number | boolean;
157
+ }): Promise<any>;
158
+ /** `analytics.topUsers` — GET /analytics/users/top. List the most active users */
159
+ "topUsers"(args?: {
160
+ "windowDays"?: string | number | boolean;
161
+ "limit"?: string | number | boolean;
162
+ "current_timestamp"?: string | number | boolean;
163
+ "_bust"?: string | number | boolean;
164
+ }): Promise<any>;
165
+ /** `analytics.topWorkflows` — GET /analytics/workflows/top. List the most-run workflows */
166
+ "topWorkflows"(args?: {
167
+ "windowDays"?: string | number | boolean;
168
+ "limit"?: string | number | boolean;
169
+ "current_timestamp"?: string | number | boolean;
170
+ "_bust"?: string | number | boolean;
171
+ }): Promise<any>;
172
+ /** `analytics.userDetail` — GET /analytics/users/{userUlid}/detail. Detailed analytics for a single user */
173
+ "userDetail"(args: {
174
+ "userUlid": string | number;
175
+ "current_timestamp"?: string | number | boolean;
176
+ "_bust"?: string | number | boolean;
177
+ }): Promise<any>;
178
+ /** `analytics.userSnapshot` — GET /analytics/users/{userUlid}/snapshot. Activity snapshot for a single user */
179
+ "userSnapshot"(args: {
180
+ "userUlid": string | number;
181
+ "current_timestamp"?: string | number | boolean;
182
+ "_bust"?: string | number | boolean;
183
+ }): Promise<any>;
184
+ /** `analytics.writeForUser` — POST /analytics/write-for-user. Write an analytics event for a subject user under a grant */
185
+ "writeForUser"(args: {
186
+ body: unknown;
187
+ }): Promise<any>;
188
+ };
189
+ "blobBuckets": {
190
+ /** `blobBuckets.createBucket` — POST /blob-buckets. Create a blob bucket */
191
+ "createBucket"(args: {
192
+ body: unknown;
193
+ }): Promise<any>;
194
+ /** `blobBuckets.delete` — DELETE /blob-buckets/{bucketId}/blobs/{blobId}. Delete a blob */
195
+ "delete"(args: {
196
+ "bucketId": string | number;
197
+ "blobId": string | number;
198
+ }): Promise<any>;
199
+ /** `blobBuckets.deleteBatch` — POST /blob-buckets/{bucketId}/blobs/delete. Batch delete blobs from a bucket */
200
+ "deleteBatch"(args: {
201
+ "bucketId": string | number;
202
+ body: unknown;
203
+ }): Promise<any>;
204
+ /** `blobBuckets.deleteBucket` — DELETE /blob-buckets/{bucketId}. Delete a blob bucket */
205
+ "deleteBucket"(args: {
206
+ "bucketId": string | number;
207
+ }): Promise<any>;
208
+ /** `blobBuckets.download` — GET /blob-buckets/{bucketId}/blobs/{blobId}. Download a blob (anonymous allowed for public buckets) */
209
+ "download"(args: {
210
+ "bucketId": string | number;
211
+ "blobId": string | number;
212
+ "disposition"?: string | number | boolean;
213
+ }): Promise<Uint8Array>;
214
+ /** `blobBuckets.downloadSigned` — GET /blob-buckets/{bucketId}/blobs/{blobId}/signed. Download a blob via a signed URL token */
215
+ "downloadSigned"(args: {
216
+ "bucketId": string | number;
217
+ "blobId": string | number;
218
+ "token"?: string | number | boolean;
219
+ }): Promise<Uint8Array>;
220
+ /** `blobBuckets.getBucket` — GET /blob-buckets/{bucketId}. Get a blob bucket by id */
221
+ "getBucket"(args: {
222
+ "bucketId": string | number;
223
+ }): Promise<any>;
224
+ /** `blobBuckets.getMetadata` — GET /blob-buckets/{bucketId}/blobs/{blobId}/metadata. Get blob metadata (anonymous allowed for public buckets) */
225
+ "getMetadata"(args: {
226
+ "bucketId": string | number;
227
+ "blobId": string | number;
228
+ }): Promise<any>;
229
+ /** `blobBuckets.getSignedUrl` — POST /blob-buckets/{bucketId}/blobs/{blobId}/signed-url. Create a signed download URL for a blob */
230
+ "getSignedUrl"(args: {
231
+ "bucketId": string | number;
232
+ "blobId": string | number;
233
+ body?: unknown;
234
+ }): Promise<any>;
235
+ /** `blobBuckets.list` — GET /blob-buckets/{bucketId}/blobs. List blobs in a bucket (anonymous allowed for public buckets) */
236
+ "list"(args: {
237
+ "bucketId": string | number;
238
+ "limit"?: string | number | boolean;
239
+ "cursor"?: string | number | boolean;
240
+ }): Promise<any>;
241
+ /** `blobBuckets.listBuckets` — GET /blob-buckets. List blob buckets */
242
+ "listBuckets"(args?: Record<string, never>): Promise<any>;
243
+ /** `blobBuckets.updateBucket` — PATCH /blob-buckets/{bucketId}. Update a blob bucket */
244
+ "updateBucket"(args: {
245
+ "bucketId": string | number;
246
+ body: unknown;
247
+ }): Promise<any>;
248
+ /** `blobBuckets.upload` — POST /blob-buckets/{bucketId}/blobs. Upload a blob to a bucket */
249
+ "upload"(args: {
250
+ "bucketId": string | number;
251
+ "X-Blob-Filename": string | number;
252
+ "X-Blob-Tags"?: string | number;
253
+ "Content-Type"?: string | number;
254
+ body: ArrayBuffer | Uint8Array | string;
255
+ }): Promise<any>;
256
+ };
257
+ "channels": {
258
+ /** `channels.authorize` — POST /channels/{channel}/grants. Issue a channel grant under a server function's grant */
259
+ "authorize"(args: {
260
+ "channel": string | number;
261
+ body: unknown;
262
+ }): Promise<any>;
263
+ /** `channels.publish` — POST /channels/{channel}/publish. Publish a message to a channel under a server function's grant */
264
+ "publish"(args: {
265
+ "channel": string | number;
266
+ body: unknown;
267
+ }): Promise<any>;
268
+ };
269
+ "collections": {
270
+ /** `collections.addDocument` — POST /collections/{collectionId}/documents. Add a document to a collection */
271
+ "addDocument"(args: {
272
+ "collectionId": string | number;
273
+ body: unknown;
274
+ }): Promise<any>;
275
+ /** `collections.addMember` — POST /collections/{collectionId}/members. Add a member to a collection */
276
+ "addMember"(args: {
277
+ "collectionId": string | number;
278
+ body: unknown;
279
+ }): Promise<any>;
280
+ /** `collections.create` — POST /collections. Create a collection */
281
+ "create"(args: {
282
+ body: unknown;
283
+ }): Promise<any>;
284
+ /** `collections.delete` — DELETE /collections/{collectionId}. Delete a collection */
285
+ "delete"(args: {
286
+ "collectionId": string | number;
287
+ }): Promise<any>;
288
+ /** `collections.get` — GET /collections/{collectionId}. Get a collection by id */
289
+ "get"(args: {
290
+ "collectionId": string | number;
291
+ }): Promise<any>;
292
+ /** `collections.getAccess` — GET /collections/{collectionId}/access. Get a collection's access summary */
293
+ "getAccess"(args: {
294
+ "collectionId": string | number;
295
+ }): Promise<any>;
296
+ /** `collections.grantGroupPermission` — POST /collections/{collectionId}/group-permissions. Grant a group permission on a collection */
297
+ "grantGroupPermission"(args: {
298
+ "collectionId": string | number;
299
+ body: unknown;
300
+ }): Promise<any>;
301
+ /** `collections.list` — GET /collections. List collections */
302
+ "list"(args?: {
303
+ "limit"?: string | number | boolean;
304
+ "cursor"?: string | number | boolean;
305
+ }): Promise<any>;
306
+ /** `collections.listCollectionsForDocument` — GET /documents/{documentId}/collections. List collections a document belongs to (reverse lookup) */
307
+ "listCollectionsForDocument"(args: {
308
+ "documentId": string | number;
309
+ }): Promise<any>;
310
+ /** `collections.listDocuments` — GET /collections/{collectionId}/documents. List documents in a collection */
311
+ "listDocuments"(args: {
312
+ "collectionId": string | number;
313
+ "limit"?: string | number | boolean;
314
+ "cursor"?: string | number | boolean;
315
+ }): Promise<any>;
316
+ /** `collections.listPendingInvitations` — GET /collections/{collectionId}/pending-invitations. List a collection's pending invitations */
317
+ "listPendingInvitations"(args: {
318
+ "collectionId": string | number;
319
+ }): Promise<any>;
320
+ /** `collections.removeDocument` — DELETE /collections/{collectionId}/documents/{documentId}. Remove a document from a collection */
321
+ "removeDocument"(args: {
322
+ "collectionId": string | number;
323
+ "documentId": string | number;
324
+ }): Promise<any>;
325
+ /** `collections.removeMember` — DELETE /collections/{collectionId}/members/{userId}. Remove a member from a collection */
326
+ "removeMember"(args: {
327
+ "collectionId": string | number;
328
+ "userId": string | number;
329
+ }): Promise<any>;
330
+ /** `collections.revokeGroupPermission` — DELETE /collections/{collectionId}/group-permissions/{groupType}/{groupId}. Revoke a group permission on a collection */
331
+ "revokeGroupPermission"(args: {
332
+ "collectionId": string | number;
333
+ "groupType": string | number;
334
+ "groupId": string | number;
335
+ }): Promise<any>;
336
+ /** `collections.update` — PATCH /collections/{collectionId}. Update a collection */
337
+ "update"(args: {
338
+ "collectionId": string | number;
339
+ body: unknown;
340
+ }): Promise<any>;
341
+ };
342
+ "configVars": {
343
+ /** `configVars.get` — GET /config-vars/{name}. Read an app config var under a server function's grants */
344
+ "get"(args: {
345
+ "name": string | number;
346
+ }): Promise<any>;
347
+ };
348
+ "connections": {
349
+ /** `connections.send` — POST /connections/{connectionId}/send. Send a realtime message to one connection of this app */
350
+ "send"(args: {
351
+ "connectionId": string | number;
352
+ body: unknown;
353
+ }): Promise<any>;
354
+ };
54
355
  "databaseTypeConfigs": {
55
356
  /** `databaseTypeConfigs.create` — POST /databases/types. Create a database type */
56
357
  "create"(args: {
@@ -341,6 +642,12 @@ export interface FunctionApi {
341
642
  "databaseId": string | number;
342
643
  "userId": string | number;
343
644
  }): Promise<any>;
645
+ /** `databases.runOperation` — POST /databases/{databaseId}/operations/{name}/run. Run a database operation under a server function's grants */
646
+ "runOperation"(args: {
647
+ "databaseId": string | number;
648
+ "name": string | number;
649
+ body?: unknown;
650
+ }): Promise<any>;
344
651
  /** `databases.transferOwnership` — POST /databases/{databaseId}/permissions/transfer. Transfer database ownership */
345
652
  "transferOwnership"(args: {
346
653
  "databaseId": string | number;
@@ -439,11 +746,6 @@ export interface FunctionApi {
439
746
  "create"(args: {
440
747
  body: unknown;
441
748
  }): Promise<any>;
442
- /** `documents.createInvitation` — POST /documents/{documentId}/invitations. Send an invitation to collaborate on a document */
443
- "createInvitation"(args: {
444
- "documentId": string | number;
445
- body: unknown;
446
- }): Promise<any>;
447
749
  /** `documents.createWithAlias` — POST /documents/create-with-alias. Create a document and bind it to an alias */
448
750
  "createWithAlias"(args: {
449
751
  body: unknown;
@@ -475,20 +777,10 @@ export interface FunctionApi {
475
777
  "debugStats"(args: {
476
778
  "documentId": string | number;
477
779
  }): Promise<any>;
478
- /** `documents.declineInvitation` — POST /documents/{documentId}/invitations/{invitationId}/decline. Decline a document invitation (invitee only) */
479
- "declineInvitation"(args: {
480
- "documentId": string | number;
481
- "invitationId": string | number;
482
- }): Promise<any>;
483
780
  /** `documents.delete` — DELETE /documents/{documentId}. Delete a document */
484
781
  "delete"(args: {
485
782
  "documentId": string | number;
486
783
  }): Promise<any>;
487
- /** `documents.deleteInvitation` — DELETE /documents/{documentId}/invitations/{invitationId}. Cancel a pending document invitation */
488
- "deleteInvitation"(args: {
489
- "documentId": string | number;
490
- "invitationId": string | number;
491
- }): Promise<any>;
492
784
  /** `documents.denyAccessRequest` — POST /documents/{documentId}/access-requests/{requestId}/deny. Deny a document access request */
493
785
  "denyAccessRequest"(args: {
494
786
  "documentId": string | number;
@@ -556,8 +848,6 @@ export interface FunctionApi {
556
848
  "documentId": string | number;
557
849
  body: unknown;
558
850
  }): Promise<any>;
559
- /** `documents.list` — GET /documents. List documents the caller can access */
560
- "list"(args?: Record<string, never>): Promise<any>;
561
851
  /** `documents.listAccessRequests` — GET /documents/{documentId}/access-requests. List access requests for a document */
562
852
  "listAccessRequests"(args: {
563
853
  "documentId": string | number;
@@ -566,10 +856,6 @@ export interface FunctionApi {
566
856
  "listGroupPermissions"(args: {
567
857
  "documentId": string | number;
568
858
  }): Promise<any>;
569
- /** `documents.listInvitations` — GET /documents/{documentId}/invitations. List invitations issued for a document */
570
- "listInvitations"(args: {
571
- "documentId": string | number;
572
- }): Promise<any>;
573
859
  /** `documents.listPendingInvitations` — GET /documents/{documentId}/pending-invitations. List a document's pending (deferred) invitations */
574
860
  "listPendingInvitations"(args: {
575
861
  "documentId": string | number;
@@ -594,6 +880,7 @@ export interface FunctionApi {
594
880
  "count"(args: {
595
881
  "documentId": string | number;
596
882
  "model": string | number;
883
+ "filter"?: Record<string, unknown> | string;
597
884
  }): Promise<any>;
598
885
  /** `documents.records.delete` — DELETE /documents/{documentId}/records/{model}/{recordId}. Delete a record from a document model */
599
886
  "delete"(args: {
@@ -612,6 +899,9 @@ export interface FunctionApi {
612
899
  "query"(args: {
613
900
  "documentId": string | number;
614
901
  "model": string | number;
902
+ "limit"?: string | number | boolean;
903
+ "cursor"?: string | number | boolean;
904
+ "filter"?: Record<string, unknown> | string;
615
905
  }): Promise<any>;
616
906
  /** `documents.records.save` — POST /documents/{documentId}/records/{model}. Create or replace a record in a document model */
617
907
  "save"(args: {
@@ -666,24 +956,582 @@ export interface FunctionApi {
666
956
  "documentId": string | number;
667
957
  body: unknown;
668
958
  }): Promise<any>;
669
- /** `documents.validateAccess` — POST /documents/{documentId}/validate-access. Validate access to a document (accepts/upgrades pending invitations) */
959
+ /** `documents.validateAccess` — POST /documents/{documentId}/validate-access. Resolve the caller's effective access level for a document */
670
960
  "validateAccess"(args: {
671
961
  "documentId": string | number;
672
962
  }): Promise<any>;
673
963
  };
964
+ "email": {
965
+ /** `email.send` — POST /emails/send. Send an application email under a server function's grant */
966
+ "send"(args: {
967
+ body: unknown;
968
+ }): Promise<any>;
969
+ };
674
970
  "functions": {
675
- /** `functions.invoke` — POST /functions/{functionKey}. Invoke a server function synchronously and return its result */
971
+ /** `functions.invoke` — POST /functions/{functionKey}. Invoke a server function: a synchronous one returns its result, a durable one starts a run */
676
972
  "invoke"(args: {
677
973
  "functionKey": string | number;
678
974
  body?: unknown;
679
975
  }): Promise<any>;
680
976
  };
977
+ "gemini": {
978
+ /** `gemini.countTokens` — POST /gemini/count-tokens. Count tokens for a Gemini request */
979
+ "countTokens"(args: {
980
+ body: unknown;
981
+ }): Promise<any>;
982
+ /** `gemini.generate` — POST /gemini/generate. Generate content with Gemini */
983
+ "generate"(args: {
984
+ body: unknown;
985
+ }): Promise<any>;
986
+ /** `gemini.generateRaw` — POST /gemini/generate-raw. Generate content with Gemini (raw passthrough) */
987
+ "generateRaw"(args: {
988
+ "model"?: string | number | boolean;
989
+ body: unknown;
990
+ }): Promise<any>;
991
+ /** `gemini.models` — GET /gemini/models. List available Gemini models */
992
+ "models"(args?: Record<string, never>): Promise<any>;
993
+ };
994
+ "groups": {
995
+ /** `groups.addMember` — POST /groups/{groupType}/{groupId}/members. Add a member to a group */
996
+ "addMember"(args: {
997
+ "groupType": string | number;
998
+ "groupId": string | number;
999
+ body: unknown;
1000
+ }): Promise<any>;
1001
+ /** `groups.create` — POST /groups. Create a group */
1002
+ "create"(args: {
1003
+ body: unknown;
1004
+ }): Promise<any>;
1005
+ /** `groups.delete` — DELETE /groups/{groupType}/{groupId}. Delete a group */
1006
+ "delete"(args: {
1007
+ "groupType": string | number;
1008
+ "groupId": string | number;
1009
+ }): Promise<any>;
1010
+ /** `groups.get` — GET /groups/{groupType}/{groupId}. Get a group by type and id */
1011
+ "get"(args: {
1012
+ "groupType": string | number;
1013
+ "groupId": string | number;
1014
+ }): Promise<any>;
1015
+ /** `groups.list` — GET /groups. List groups */
1016
+ "list"(args?: {
1017
+ "type"?: string | number | boolean;
1018
+ "includeSystem"?: string | number | boolean;
1019
+ "limit"?: string | number | boolean;
1020
+ "cursor"?: string | number | boolean;
1021
+ }): Promise<any>;
1022
+ /** `groups.listDatabases` — GET /groups/{groupType}/{groupId}/databases. List databases shared with a group */
1023
+ "listDatabases"(args: {
1024
+ "groupType": string | number;
1025
+ "groupId": string | number;
1026
+ }): Promise<any>;
1027
+ /** `groups.listDocuments` — GET /groups/{groupType}/{groupId}/documents. List documents shared with a group */
1028
+ "listDocuments"(args: {
1029
+ "groupType": string | number;
1030
+ "groupId": string | number;
1031
+ }): Promise<any>;
1032
+ /** `groups.listMembers` — GET /groups/{groupType}/{groupId}/members. List a group's members */
1033
+ "listMembers"(args: {
1034
+ "groupType": string | number;
1035
+ "groupId": string | number;
1036
+ "limit"?: string | number | boolean;
1037
+ "cursor"?: string | number | boolean;
1038
+ "include"?: string | number | boolean;
1039
+ }): Promise<any>;
1040
+ /** `groups.listPendingInvitations` — GET /groups/{groupType}/{groupId}/pending-invitations. List a group's pending invitations */
1041
+ "listPendingInvitations"(args: {
1042
+ "groupType": string | number;
1043
+ "groupId": string | number;
1044
+ }): Promise<any>;
1045
+ /** `groups.listUserMemberships` — GET /users/{userId}/memberships. List a user's group memberships */
1046
+ "listUserMemberships"(args: {
1047
+ "userId": string | number;
1048
+ "includeSystem"?: string | number | boolean;
1049
+ "type"?: string | number | boolean;
1050
+ }): Promise<any>;
1051
+ /** `groups.removeMember` — DELETE /groups/{groupType}/{groupId}/members/{userId}. Remove a specific member from a group */
1052
+ "removeMember"(args: {
1053
+ "groupType": string | number;
1054
+ "groupId": string | number;
1055
+ "userId": string | number;
1056
+ }): Promise<any>;
1057
+ /** `groups.removeMemberByEmail` — DELETE /groups/{groupType}/{groupId}/members. Remove a group member or pending invitation by email */
1058
+ "removeMemberByEmail"(args: {
1059
+ "groupType": string | number;
1060
+ "groupId": string | number;
1061
+ "email"?: string | number | boolean;
1062
+ }): Promise<any>;
1063
+ /** `groups.update` — PATCH /groups/{groupType}/{groupId}. Update a group */
1064
+ "update"(args: {
1065
+ "groupType": string | number;
1066
+ "groupId": string | number;
1067
+ body: unknown;
1068
+ }): Promise<any>;
1069
+ };
1070
+ "integrations": {
1071
+ /** `integrations.call` — POST /integrations/{integrationKey}/call. Call an integration under a server function's grants */
1072
+ "call"(args: {
1073
+ "integrationKey": string | number;
1074
+ body?: unknown;
1075
+ }): Promise<any>;
1076
+ /** `integrations.list` — GET /integrations. List integrations configured for the app */
1077
+ "list"(args?: Record<string, never>): Promise<any>;
1078
+ /** `integrations.proxy` — POST /integrations/{integrationKey}/proxy. Proxy a request through a configured integration */
1079
+ "proxy"(args: {
1080
+ "integrationKey": string | number;
1081
+ }): Promise<any>;
1082
+ };
1083
+ "iterations": {
1084
+ /** `iterations.get` — GET /iterations/{iterationName}. Get details for a single iteration */
1085
+ "get"(args: {
1086
+ "iterationName": string | number;
1087
+ }): Promise<any>;
1088
+ /** `iterations.list` — GET /iterations. List iteration runs */
1089
+ "list"(args?: Record<string, never>): Promise<any>;
1090
+ /** `iterations.reset` — POST /iterations/{iterationName}/reset. Reset a terminal iteration so the next trigger runs fresh */
1091
+ "reset"(args: {
1092
+ "iterationName": string | number;
1093
+ }): Promise<any>;
1094
+ };
1095
+ "llm": {
1096
+ /** `llm.chat` — POST /llm/chat. Run an LLM chat completion */
1097
+ "chat"(args: {
1098
+ body: unknown;
1099
+ }): Promise<any>;
1100
+ /** `llm.models` — GET /llm/models. List available LLM models */
1101
+ "models"(args?: Record<string, never>): Promise<any>;
1102
+ };
1103
+ "locks": {
1104
+ /** `locks.list` — GET /locks. List all currently-held named locks in the app */
1105
+ "list"(args?: Record<string, never>): Promise<any>;
1106
+ /** `locks.release` — POST /locks/release. Release a held named lock (requires the acquire handle) */
1107
+ "release"(args: {
1108
+ body: unknown;
1109
+ }): Promise<any>;
1110
+ /** `locks.renew` — POST /locks/renew. Extend the lease on a held named lock */
1111
+ "renew"(args: {
1112
+ body: unknown;
1113
+ }): Promise<any>;
1114
+ /** `locks.status` — POST /locks/status. Report the current holder of a named lock */
1115
+ "status"(args: {
1116
+ body: unknown;
1117
+ }): Promise<any>;
1118
+ /** `locks.tryAcquire` — POST /locks/acquire. Attempt to acquire a named lock (single non-blocking attempt) */
1119
+ "tryAcquire"(args: {
1120
+ body: unknown;
1121
+ }): Promise<any>;
1122
+ };
1123
+ "notifications": {
1124
+ /** `notifications.list` — GET /notifications. List the caller's in-app notification inbox (newest first) */
1125
+ "list"(args?: {
1126
+ "limit"?: string | number | boolean;
1127
+ "cursor"?: string | number | boolean;
1128
+ }): Promise<any>;
1129
+ /** `notifications.listDevices` — GET /me/push-tokens. List the caller's registered push devices */
1130
+ "listDevices"(args?: Record<string, never>): Promise<any>;
1131
+ /** `notifications.markAllRead` — POST /notifications/read-all. Mark all of the caller's notifications as read */
1132
+ "markAllRead"(args?: Record<string, never>): Promise<any>;
1133
+ /** `notifications.markRead` — PATCH /notifications/{notificationId}/read. Mark one of the caller's notifications as read */
1134
+ "markRead"(args: {
1135
+ "notificationId": string | number;
1136
+ }): Promise<any>;
1137
+ /** `notifications.registerDevice` — POST /me/push-tokens. Register (upsert) the caller's device push token */
1138
+ "registerDevice"(args: {
1139
+ body: unknown;
1140
+ }): Promise<any>;
1141
+ /** `notifications.send` — POST /notifications/send. Send a notification to an app user (durable in-app inbox row + live WS frame) */
1142
+ "send"(args: {
1143
+ body: unknown;
1144
+ }): Promise<any>;
1145
+ /** `notifications.unreadCount` — GET /notifications/unread-count. Count the caller's unread notifications */
1146
+ "unreadCount"(args?: Record<string, never>): Promise<any>;
1147
+ /** `notifications.unregisterDevice` — DELETE /me/push-tokens/{token}. Unregister one of the caller's device push tokens */
1148
+ "unregisterDevice"(args: {
1149
+ "token": string | number;
1150
+ }): Promise<any>;
1151
+ };
1152
+ "prompts": {
1153
+ /** `prompts.execute` — POST /prompts/{promptKey}/execute. Execute a saved prompt */
1154
+ "execute"(args: {
1155
+ "promptKey": string | number;
1156
+ }): Promise<any>;
1157
+ /** `prompts.run` — POST /prompts/{promptKey}/run. Run a prompt under a server function's grants */
1158
+ "run"(args: {
1159
+ "promptKey": string | number;
1160
+ body?: unknown;
1161
+ }): Promise<any>;
1162
+ };
1163
+ "resourceMetadata": {
1164
+ /** `resourceMetadata.delete` — DELETE /resources/{resourceType}/{resourceId}/metadata/{category}. Delete a resource's metadata for a category (idempotent) */
1165
+ "delete"(args: {
1166
+ "resourceType": string | number;
1167
+ "resourceId": string | number;
1168
+ "category": string | number;
1169
+ }): Promise<any>;
1170
+ /** `resourceMetadata.get` — GET /resources/{resourceType}/{resourceId}/metadata/{category}. Read a resource's metadata for a category */
1171
+ "get"(args: {
1172
+ "resourceType": string | number;
1173
+ "resourceId": string | number;
1174
+ "category": string | number;
1175
+ }): Promise<any>;
1176
+ /** `resourceMetadata.getBatch` — POST /resources/metadata/batch. Batch-read resource metadata (partial success) */
1177
+ "getBatch"(args: {
1178
+ "debug"?: string | number | boolean;
1179
+ body: unknown;
1180
+ }): Promise<any>;
1181
+ /** `resourceMetadata.list` — GET /resources/{resourceType}/{resourceId}/metadata. List all metadata categories on a resource */
1182
+ "list"(args: {
1183
+ "resourceType": string | number;
1184
+ "resourceId": string | number;
1185
+ }): Promise<any>;
1186
+ /** `resourceMetadata.resolve` — POST /metadata/resolve. Resolve a resource by a category's unique metadata value */
1187
+ "resolve"(args: {
1188
+ body: unknown;
1189
+ }): Promise<any>;
1190
+ /** `resourceMetadata.set` — PUT /resources/{resourceType}/{resourceId}/metadata/{category}. Write a resource's metadata for a category */
1191
+ "set"(args: {
1192
+ "resourceType": string | number;
1193
+ "resourceId": string | number;
1194
+ "category": string | number;
1195
+ body: unknown;
1196
+ }): Promise<any>;
1197
+ };
1198
+ "secrets": {
1199
+ /** `secrets.get` — GET /secrets/{name}. Read an app secret under a server function's grants */
1200
+ "get"(args: {
1201
+ "name": string | number;
1202
+ }): Promise<any>;
1203
+ };
1204
+ "users": {
1205
+ /** `users.getBasic` — GET /users/{userId}/basic. Get a user's basic public profile */
1206
+ "getBasic"(args: {
1207
+ "userId": string | number;
1208
+ }): Promise<any>;
1209
+ /** `users.getProfiles` — POST /users/profiles. Look up user profiles in bulk */
1210
+ "getProfiles"(args: {
1211
+ body: unknown;
1212
+ }): Promise<any>;
1213
+ /** `users.list` — GET /users. List app users */
1214
+ "list"(args?: {
1215
+ "email"?: string | number | boolean;
1216
+ "userId"?: string | number | boolean;
1217
+ "name"?: string | number | boolean;
1218
+ "limit"?: string | number | boolean;
1219
+ "cursor"?: string | number | boolean;
1220
+ }): Promise<any>;
1221
+ /** `users.lookup` — GET /users/lookup. Look up a user by identifier */
1222
+ "lookup"(args?: {
1223
+ "email"?: string | number | boolean;
1224
+ }): Promise<any>;
1225
+ /** `users.remove` — DELETE /users/{userId}. Remove a user from the app */
1226
+ "remove"(args: {
1227
+ "userId": string | number;
1228
+ }): Promise<any>;
1229
+ /** `users.send` — POST /users/{userId}/send. Send a realtime message to a member of this app */
1230
+ "send"(args: {
1231
+ "userId": string | number;
1232
+ body: unknown;
1233
+ }): Promise<any>;
1234
+ /** `users.setRole` — PUT /users/{userId}/role. Set a user's app role */
1235
+ "setRole"(args: {
1236
+ "userId": string | number;
1237
+ body: unknown;
1238
+ }): Promise<any>;
1239
+ };
1240
+ }
1241
+
1242
+ /**
1243
+ * The app's database types, model by model — EMPTY here and filled by the
1244
+ * declaration `primitive config push` writes into the config tree's
1245
+ * `functions/` directory (#3182). A tree that has pushed once therefore types
1246
+ * `ctx.db` against its own schemas; one that has not gets the open form and
1247
+ * still compiles.
1248
+ */
1249
+ export interface DatabaseSchemas {}
1250
+
1251
+ /**
1252
+ * The type keys `ctx.db` accepts.
1253
+ *
1254
+ * Every key the augmentation declares, and NOTHING else, once a tree has
1255
+ * pushed: naming a type the app does not have is a compile error. Before that
1256
+ * — an unaugmented tree, a function written before the first push —
1257
+ * `DatabaseSchemas` is empty and `keyof` it is `never`, which would make the
1258
+ * argument unwritable rather than open. So the empty case widens to `string`
1259
+ * and the handle is untyped instead of unusable.
1260
+ */
1261
+ export type DatabaseTypeKeys = [keyof DatabaseSchemas] extends [never]
1262
+ ? string
1263
+ : keyof DatabaseSchemas & string;
1264
+
1265
+ /** A model's row shape, or an open record for a type with no schema. */
1266
+ export type DatabaseModels<K extends string> = K extends keyof DatabaseSchemas
1267
+ ? DatabaseSchemas[K]
1268
+ : Record<string, Record<string, unknown>>;
1269
+
1270
+ /**
1271
+ * The model names one database type addresses — its own, or any name at all
1272
+ * for a type that declares no schema (the DO is schemaless, so a grant on such
1273
+ * a type is legal and there is simply nothing to narrow to).
1274
+ */
1275
+ export type DatabaseModelNames<TModels> = [keyof TModels] extends [never]
1276
+ ? string
1277
+ : keyof TModels & string;
1278
+
1279
+ /** One model's row shape, open when the type declares no schema. */
1280
+ export type DatabaseModelRow<TModels, M extends string> =
1281
+ M extends keyof TModels ? TModels[M] : Record<string, unknown>;
1282
+
1283
+ /**
1284
+ * One model of one database, scoped: the `modelName` every records call needs
1285
+ * is bound once, so a call names an operation and its own arguments.
1286
+ *
1287
+ * Filters and options are ordinary objects. Inline filter typing arrives with
1288
+ * unified schemas — the intent's decision, not an omission — so what is typed
1289
+ * today is the model name and the row shape a read hands back.
1290
+ */
1291
+ export type ModelScopedBody<TBody = Record<string, unknown>> = TBody & {
1292
+ /**
1293
+ * Bound by the handle. Reserved so a body cannot look like a second way to
1294
+ * choose a model: the runtime assigns the handle's own model last, and a row
1295
+ * type that said otherwise would be a lie about what came back.
1296
+ */
1297
+ modelName?: never;
1298
+ };
1299
+
1300
+ /**
1301
+ * One write inside a model-scoped batch. Every batch verb is a write, so a
1302
+ * batch on a model needs that model's write grant whatever the ops are.
1303
+ */
1304
+ export type ModelBatchOperation<TRow> = {
1305
+ op: "save" | "patch" | "delete" | "increment" | "addToSet" | "removeFromSet";
1306
+ id?: string;
1307
+ data?: Partial<TRow>;
1308
+ /** Bound by the handle, for the same reason every other call's is. */
1309
+ modelName?: never;
1310
+ [key: string]: unknown;
1311
+ };
1312
+
1313
+ export interface DatabaseModelHandle<TRow> {
1314
+ query(body?: ModelScopedBody): Promise<{ data: TRow[]; hasMore?: boolean; nextCursor?: string }>;
1315
+ count(body?: ModelScopedBody): Promise<any>;
1316
+ aggregate(body: ModelScopedBody): Promise<any>;
1317
+ save(body: ModelScopedBody<{ id?: string; data: Partial<TRow>; [key: string]: unknown }>): Promise<any>;
1318
+ /** Several writes to THIS model in one request; the binding is per item. */
1319
+ batch(body: {
1320
+ operations: Array<ModelBatchOperation<TRow>>;
1321
+ atomic?: boolean;
1322
+ }): Promise<any>;
1323
+ }
1324
+
1325
+ /** One database, typed by the database TYPE key its grant names. */
1326
+ export interface DatabaseHandle<TModels> {
1327
+ /** The database this handle addresses — part of every query cache key. */
1328
+ databaseId: string;
1329
+ /** The type key this handle was opened with. */
1330
+ databaseType: string;
1331
+ model<M extends DatabaseModelNames<TModels>>(
1332
+ modelName: M
1333
+ ): DatabaseModelHandle<DatabaseModelRow<TModels, M>>;
1334
+ /**
1335
+ * A batch may also SPAN models, and then it names each one itself — the
1336
+ * model-scoped form above is the typed way to write the common case.
1337
+ */
1338
+ batch(body: {
1339
+ operations: Array<Record<string, unknown>>;
1340
+ atomic?: boolean;
1341
+ }): Promise<any>;
1342
+ /** Run one of the database type's prepared operations under this function's grants. */
1343
+ runOperation(name: string, params?: Record<string, unknown>): Promise<any>;
1344
+ }
1345
+
1346
+ /**
1347
+ * One call through a granted integration — #3183.
1348
+ *
1349
+ * `path` is RELATIVE to the integration's `baseUrl`, and the host is the
1350
+ * integration's: the platform resolves and normalizes the path before checking
1351
+ * it against `allowedPaths`, so a protocol-relative path or a `..` segment is
1352
+ * refused rather than silently sending the request somewhere else.
1353
+ */
1354
+ export interface IntegrationCallRequest {
1355
+ /** Must be in the integration's `allowedMethods`. Defaults to its own default. */
1356
+ method?: string;
1357
+ /** Relative to the integration's `baseUrl`. */
1358
+ path?: string;
1359
+ query?: Record<string, unknown>;
1360
+ headers?: Record<string, string>;
1361
+ body?: unknown;
1362
+ /** form-urlencoded body; not combinable with `body`. */
1363
+ form?: Record<string, unknown>;
1364
+ bodyMode?: "json" | "raw" | "multipart";
1365
+ }
1366
+
1367
+ /** What the upstream answered, as the platform saw it. */
1368
+ export interface IntegrationCallResult {
1369
+ status: number;
1370
+ headers: Record<string, string>;
1371
+ body: unknown;
1372
+ durationMs: number;
1373
+ traceId: string;
1374
+ /** Set when the platform or the upstream refused; absent on success. */
1375
+ errorCode?: string;
1376
+ }
1377
+
1378
+ /** What a prompt run is given — the member execute route's own body. */
1379
+ export interface PromptRunArgs {
1380
+ variables?: Record<string, unknown>;
1381
+ /** Run against a different model for this call only. */
1382
+ modelOverride?: string;
1383
+ /** Pin a specific config OF THIS PROMPT; defaults to its active one. */
1384
+ configId?: string;
1385
+ }
1386
+
1387
+ /** The member execute envelope, unchanged on the function path. */
1388
+ export interface PromptRunResult {
1389
+ success: boolean;
1390
+ output: string;
1391
+ error?: string | null;
1392
+ metrics?: Record<string, unknown>;
1393
+ configId: string;
1394
+ }
1395
+
1396
+ /** What a send delivered to. */
1397
+ export interface SendResult {
1398
+ /** Unique live connections the frame was sent to. Zero is a success. */
1399
+ connections: number;
1400
+ /** True when the per-user connection ceiling stopped the fanout. */
1401
+ truncated: boolean;
1402
+ }
1403
+
1404
+ /** Who a channel grant is for, and for how long — #3184. */
1405
+ export interface ChannelAuthorizeOptions {
1406
+ /**
1407
+ * The member the grant names. Caller mode defaults to the invoking caller;
1408
+ * system mode has no caller and must say.
1409
+ */
1410
+ userId?: string;
1411
+ /** Default 300 s, clamped to the platform ceiling of 900 s. */
1412
+ ttlSeconds?: number;
1413
+ }
1414
+
1415
+ /** The credential a client presents on the WebSocket it already has. */
1416
+ export interface ChannelGrantResult {
1417
+ channel: string;
1418
+ /** The signed grant. Hand it to the client; it is not readable here. */
1419
+ grant: string;
1420
+ /** Epoch milliseconds. Expiry is the only revocation a channel has. */
1421
+ expiresAt: number;
681
1422
  }
682
1423
 
683
1424
  export interface FunctionContext {
684
- user: FunctionUser;
1425
+ /** The HTTP caller, or null for a webhook, cron or database-change fire. */
1426
+ user: FunctionUser | null;
685
1427
  trigger: FunctionTrigger;
686
1428
  api: FunctionApi;
1429
+ /**
1430
+ * A typed handle over one database. The second argument is the database TYPE
1431
+ * key — the same key a `database:<type>/<model>:read|write` grant names —
1432
+ * and it is what selects the models this handle knows (#3182).
1433
+ */
1434
+ db<K extends DatabaseTypeKeys>(
1435
+ databaseId: string,
1436
+ databaseType: K
1437
+ ): DatabaseHandle<DatabaseModels<K>>;
1438
+ /**
1439
+ * Outbound HTTP, through a granted integration and nowhere else (#3183).
1440
+ *
1441
+ * A function has no raw network access: every `fetch` to a host that is not
1442
+ * the platform answers `FUNCTION_EGRESS_DENIED`, grant or no grant. What an
1443
+ * `integration:<key>` grant buys is this call, which the MAIN WORKER makes on
1444
+ * the function's behalf — which is why the integration's `{{secrets.*}}`
1445
+ * credentials can be injected into the request without ever being visible to
1446
+ * the code that asked for it.
1447
+ */
1448
+ integrations: {
1449
+ call(
1450
+ integrationKey: string,
1451
+ request?: IntegrationCallRequest
1452
+ ): Promise<IntegrationCallResult>;
1453
+ };
1454
+ /**
1455
+ * Run one of the app's prompts, under a `prompt:<key>` grant (#3183).
1456
+ *
1457
+ * The prompt's `accessRule` is not consulted — it states which MEMBERS may
1458
+ * execute the prompt from a client, and the grant is what authorizes this
1459
+ * call. The provider request is bounded by the invocation's own deadline.
1460
+ */
1461
+ prompts: {
1462
+ run(promptKey: string, args?: PromptRunArgs): Promise<PromptRunResult>;
1463
+ };
1464
+ /**
1465
+ * The value of one app secret, under a `secret:<NAME>` grant (#3183).
1466
+ *
1467
+ * Prefer `ctx.integrations.call` when the secret is a credential FOR an
1468
+ * integration: there the value is injected by the platform and never enters
1469
+ * the sandbox at all. This is for the cases an integration cannot express.
1470
+ *
1471
+ * Rejects with `FUNCTION_SECRET_NOT_FOUND` when the grant names a value
1472
+ * this environment has not been given — secrets are provisioned per
1473
+ * environment, out of band from the push.
1474
+ */
1475
+ secret(name: string): Promise<string>;
1476
+ /** The value of one app config var, under a `var:<NAME>` grant (#3183). */
1477
+ configVar(name: string): Promise<string>;
1478
+ /**
1479
+ * Realtime messages to this app's clients, under a `users:send` grant
1480
+ * (#3183).
1481
+ *
1482
+ * The recipient's client receives a `direct.message` frame carrying this
1483
+ * payload and the sending function's key. Presence is not guaranteed: a
1484
+ * member with no live socket answers `{ connections: 0 }` rather than an
1485
+ * error.
1486
+ */
1487
+ users: {
1488
+ send(userId: string, payload: unknown): Promise<SendResult>;
1489
+ };
1490
+ /** The same frame to ONE connection, under a `connections:send` grant. */
1491
+ connections: {
1492
+ send(connectionId: string, payload: unknown): Promise<SendResult>;
1493
+ };
1494
+ /**
1495
+ * Realtime channels, under a `channel:<namespace>` grant that COVERS the
1496
+ * channel — #3184.
1497
+ *
1498
+ * `authorize` hands back a short-lived grant for ONE member to present on
1499
+ * the socket it already has; `publish` delivers a `channel.message` frame to
1500
+ * every connection that has joined and whose grant has not expired. Expiry is
1501
+ * the only revocation, so `expiresAt` comes back for the client to renew
1502
+ * ahead of — a renewal is another authorize and another subscribe.
1503
+ */
1504
+ channels: {
1505
+ authorize(
1506
+ channel: string,
1507
+ options?: ChannelAuthorizeOptions
1508
+ ): Promise<ChannelGrantResult>;
1509
+ publish(channel: string, payload: unknown): Promise<SendResult>;
1510
+ };
1511
+ }
1512
+
1513
+ /**
1514
+ * The durable step handle — Cloudflare Workflows' `WorkflowStep`, forwarded
1515
+ * untouched, so `step.do` results are memoized and `step.sleep` hibernates.
1516
+ *
1517
+ * A SYNCHRONOUS invocation gets a passthrough shim of the same shape, so a body
1518
+ * written for durable mode runs either way: `do` executes the body (nothing to
1519
+ * memoize into without an engine), `sleep` resolves immediately (the sync
1520
+ * ceiling is seconds), and `waitForEvent` throws `STEP_NOT_AVAILABLE`.
1521
+ */
1522
+ export interface FunctionStep {
1523
+ do<T>(name: string, body: () => T | Promise<T>): Promise<T>;
1524
+ do<T>(
1525
+ name: string,
1526
+ config: Record<string, unknown>,
1527
+ body: () => T | Promise<T>
1528
+ ): Promise<T>;
1529
+ sleep(name: string, duration: number | string): Promise<void>;
1530
+ sleepUntil(name: string, timestamp: Date | number): Promise<void>;
1531
+ waitForEvent<T = unknown>(
1532
+ name: string,
1533
+ options: Record<string, unknown>
1534
+ ): Promise<T>;
687
1535
  }
688
1536
 
689
1537
  export declare const SDK_STUB_VERSION: string;
@@ -692,15 +1540,143 @@ export declare const SDK_STUB_VERSION: string;
692
1540
  * Declare a server function's handler.
693
1541
  *
694
1542
  * Both authoring styles reach the platform bootstrap identically — a plain
695
- * default export taking `(input, ctx)` runs exactly as a wrapped one does —
696
- * so this is a typing and intent marker, not a different calling convention.
1543
+ * default export taking `(input, ctx, step)` runs exactly as a wrapped one
1544
+ * does — so this is a typing and intent marker, not a different calling
1545
+ * convention.
1546
+ *
1547
+ * `step` is the third argument in BOTH modes (#3186). A durable run gets the
1548
+ * engine's live `WorkflowStep`; a synchronous one gets a passthrough shim
1549
+ * (`do` runs the body, `sleep` resolves at once, `waitForEvent` throws a
1550
+ * named error), so a durable-authored body can be exercised synchronously.
697
1551
  */
698
1552
  export declare function defineFunction<TInput = unknown, TOutput = unknown>(
699
- handler: (input: TInput, ctx: FunctionContext) => TOutput | Promise<TOutput>
700
- ): (input: TInput, ctx: FunctionContext) => Promise<TOutput>;
1553
+ handler: (
1554
+ input: TInput,
1555
+ ctx: FunctionContext,
1556
+ step: FunctionStep
1557
+ ) => TOutput | Promise<TOutput>
1558
+ ): (
1559
+ input: TInput,
1560
+ ctx: FunctionContext,
1561
+ step: FunctionStep
1562
+ ) => Promise<TOutput>;
1563
+
1564
+ /**
1565
+ * One declared parameter of a registered query.
1566
+ *
1567
+ * `caller: true` (or `type: "$caller"`) is the intent's IMMUTABLE binding:
1568
+ * the platform injects the invocation's user and refuses a call that supplies
1569
+ * the parameter itself.
1570
+ */
1571
+ export interface QueryParamSpec {
1572
+ type?: "string" | "number" | "boolean" | "any" | "$caller";
1573
+ caller?: boolean;
1574
+ optional?: boolean;
1575
+ default?: unknown;
1576
+ }
1577
+
1578
+ export interface QueryDefinition<TDb, TParams, TResult> {
1579
+ /** The models this query touches. Caching is off unless the run matches. */
1580
+ models: string[];
1581
+ params?: Record<string, QueryParamSpec>;
1582
+ /**
1583
+ * Cache the answer for `ttlMs` — capped by the platform, and applied ONLY
1584
+ * when the run is verified: every model the body touched was declared, and
1585
+ * no prepared operation was used. A write through the handle drops the
1586
+ * entries that read the written model.
1587
+ */
1588
+ cache?: { ttlMs: number };
1589
+ run: (db: TDb, params: TParams) => TResult | Promise<TResult>;
1590
+ }
1591
+
1592
+ /**
1593
+ * Register a named query over a database handle (#3187).
1594
+ *
1595
+ * A registration helper, not a language: `run` is ordinary code over the same
1596
+ * typed handle, and what registration adds is a name, declared parameters
1597
+ * (validated, coerced and defaulted), the `$caller` binding, and a cache the
1598
+ * platform may use when it can verify what the run touched.
1599
+ *
1600
+ * Register at MODULE scope: a registration made inside the handler still works
1601
+ * for direct calls but is invisible to `config push`'s manifest.
1602
+ */
1603
+ export declare function defineQuery<
1604
+ TDb = DatabaseHandle<any>,
1605
+ TParams = Record<string, any>,
1606
+ TResult = any
1607
+ >(
1608
+ name: string,
1609
+ definition: QueryDefinition<TDb, TParams, TResult>
1610
+ ): (db: TDb, params?: Record<string, unknown>) => Promise<TResult>;
1611
+
1612
+ /** The same, for a registration that writes. A mutation is never cached. */
1613
+ export declare function defineMutation<
1614
+ TDb = DatabaseHandle<any>,
1615
+ TParams = Record<string, any>,
1616
+ TResult = any
1617
+ >(
1618
+ name: string,
1619
+ definition: QueryDefinition<TDb, TParams, TResult>
1620
+ ): (db: TDb, params?: Record<string, unknown>) => Promise<TResult>;
1621
+
1622
+ /** The platform's cap on a registered query's cache TTL, in milliseconds. */
1623
+ export declare const MAX_QUERY_CACHE_TTL_MS: number;
1624
+
1625
+ /**
1626
+ * Map over items with BOUNDED concurrency (#3187). Results come back in the
1627
+ * input's order; the first rejection wins and nothing new is started after it.
1628
+ *
1629
+ * The bound matters inside a sandbox: an unbounded fan-out over a page of rows
1630
+ * spends the whole subrequest ceiling in one line.
1631
+ */
1632
+ export declare function pMap<T, R>(
1633
+ items: Iterable<T>,
1634
+ mapper: (item: T, index: number) => R | Promise<R>,
1635
+ options?: { concurrency?: number }
1636
+ ): Promise<R[]>;
1637
+
1638
+ /** The default concurrency `pMap` applies when none is given. */
1639
+ export declare const PMAP_DEFAULT_CONCURRENCY: number;
1640
+
1641
+ /**
1642
+ * A time-ordered ULID.
1643
+ *
1644
+ * GENERATE IT INSIDE `step.do`. A durable run re-executes its handler on
1645
+ * every resume and memoizes only what a step returned, so an id minted at the
1646
+ * top of a handler is a different id after a sleep — and whatever it was
1647
+ * written into is orphaned.
1648
+ */
1649
+ export declare function ulid(): string;
1650
+
1651
+ /** One family's durable `step.do` configuration. */
1652
+ export interface StepPolicy {
1653
+ retries: { limit: number; delay: string; backoff: string };
1654
+ timeout: string;
1655
+ }
1656
+
1657
+ /**
1658
+ * The per-family step policies the DSL runners use, generated from their own
1659
+ * timeout module (#3187):
1660
+ *
1661
+ * await step.do("send-receipt", stepPolicy.email, () =>
1662
+ * ctx.api.email.send({ body: { to, subject, htmlBody } }));
1663
+ *
1664
+ * `retries: 0` on email and the AI families is deliberate — a retried timeout
1665
+ * would send the same email twice or bill the same generation twice.
1666
+ */
1667
+ export declare const stepPolicy: {
1668
+ email: StepPolicy;
1669
+ llm: StepPolicy;
1670
+ gemini: StepPolicy;
1671
+ database: StepPolicy;
1672
+ };
701
1673
 
702
1674
  declare const _default: {
703
1675
  SDK_STUB_VERSION: string;
704
1676
  defineFunction: typeof defineFunction;
1677
+ defineQuery: typeof defineQuery;
1678
+ defineMutation: typeof defineMutation;
1679
+ pMap: typeof pMap;
1680
+ ulid: typeof ulid;
705
1681
  };
706
1682
  export default _default;
package/index.js CHANGED
@@ -31,4 +31,60 @@ export function defineFunction() {
31
31
  throw new Error(NOT_PROVIDED);
32
32
  }
33
33
 
34
- export default { SDK_STUB_VERSION, defineFunction };
34
+ /**
35
+ * The platform's cap on a registered query's cache TTL. The stub carries the
36
+ * value rather than throwing, because it is a constant an author may read at
37
+ * module scope while type-checking outside a sandbox.
38
+ */
39
+ export const MAX_QUERY_CACHE_TTL_MS = 60000;
40
+
41
+ /**
42
+ * Register a named query or mutation (#3187). Same reason as above: an ESM
43
+ * named import of an export a module does not have is a LINK error, so the
44
+ * stub must declare every export the platform copy has — and refuse.
45
+ */
46
+ export function defineQuery() {
47
+ throw new Error(NOT_PROVIDED);
48
+ }
49
+
50
+ export function defineMutation() {
51
+ throw new Error(NOT_PROVIDED);
52
+ }
53
+
54
+ /** Bounded-concurrency map. The platform copy implements it; this refuses. */
55
+ export function pMap() {
56
+ throw new Error(NOT_PROVIDED);
57
+ }
58
+
59
+ /** A time-ordered ULID, minted by the platform copy. */
60
+ export function ulid() {
61
+ throw new Error(NOT_PROVIDED);
62
+ }
63
+
64
+ /**
65
+ * The per-family durable step policies. The stub carries an empty shape rather
66
+ * than throwing: it is read at module scope by code that only type-checks.
67
+ */
68
+ export const PMAP_DEFAULT_CONCURRENCY = 8;
69
+
70
+ /**
71
+ * The per-family durable step policies. The stub carries the SHAPE and empty
72
+ * values: an author may read `stepPolicy.email` at module scope while
73
+ * type-checking outside a sandbox, and throwing there would break a build that
74
+ * never runs a function. The platform copy carries the runners' real numbers.
75
+ */
76
+ export const stepPolicy = {
77
+ email: null,
78
+ llm: null,
79
+ gemini: null,
80
+ database: null,
81
+ };
82
+
83
+ export default {
84
+ SDK_STUB_VERSION,
85
+ defineFunction,
86
+ defineQuery,
87
+ defineMutation,
88
+ pMap,
89
+ ulid,
90
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primitive-functions",
3
- "version": "0.2.0-alpha.0",
3
+ "version": "0.2.0-alpha.2",
4
4
  "description": "The module a Primitive server function imports. Supplied by the platform at invoke time; reserved here so the name is ours before the runtime lands.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Primitive LLC",