primitive-functions 0.2.0-alpha.7 → 0.2.0-alpha.9

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 +383 -38
  2. package/index.js +30 -0
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -32,8 +32,15 @@ export interface FunctionUser {
32
32
  * that the fire path does not set.
33
33
  */
34
34
  export type FunctionTrigger =
35
- /** An HTTP invocation: the caller is `ctx.user`. */
36
- | { kind: "http" }
35
+ /**
36
+ * An HTTP invocation: the caller is `ctx.user`.
37
+ *
38
+ * `runKey` is the key a `start` was coalesced by (#3562) — what you
39
+ * passed as `runKey` to make the run a singleton — and null when the
40
+ * start named none, or when the call was an `invoke`, which coalesces
41
+ * nothing.
42
+ */
43
+ | { kind: "http"; runKey: string | null }
37
44
  /** A verified webhook delivery to the function's own key. */
38
45
  | {
39
46
  kind: "webhook";
@@ -56,8 +63,6 @@ export type FunctionTrigger =
56
63
  /** Present, and true, only on the diagnostic fire: nothing was scheduled. */
57
64
  manual?: true;
58
65
  }
59
- /** A write to a watched database type. */
60
- | { kind: "database"; databaseId: string; databaseType: string }
61
66
  /**
62
67
  * A DSL workflow's `workflow.call` step reached this function (#3288).
63
68
  *
@@ -71,10 +76,25 @@ export type FunctionTrigger =
71
76
  * Another function started this one with `ctx.functions.start` (#3280).
72
77
  *
73
78
  * `functionKey` names the PARENT, and `runId` its run — null when the
74
- * parent was a request invocation over HTTP, which writes no run row, or a
75
- * database-change fire, which writes none either.
79
+ * parent was a request invocation over HTTP, which writes no run row.
80
+ * `runKey` is the key the parent's start named, or null (#3562).
81
+ */
82
+ | {
83
+ kind: "function";
84
+ functionKey: string;
85
+ runId: string | null;
86
+ runKey: string | null;
87
+ }
88
+ /**
89
+ * An admin ran this function as the SYSTEM, from outside any trigger
90
+ * (#3448) — `primitive functions invoke --as system`.
91
+ *
92
+ * `ctx.user` is null and the code runs with the app's system authority, as
93
+ * it does for a cron or webhook fire. `userId` names the admin who asked:
94
+ * attribution, never the principal the code runs as. `runKey` is the key
95
+ * the admin's start named, or null (#3562).
76
96
  */
77
- | { kind: "function"; functionKey: string; runId: string | null };
97
+ | { kind: "manual"; userId: string; runKey: string | null };
78
98
 
79
99
  /** The result shape of an operation that answers in response headers. */
80
100
  export interface HeaderResult {
@@ -708,6 +728,12 @@ export interface FunctionApi {
708
728
  }): Promise<any>;
709
729
  };
710
730
  "documents": {
731
+ /** `documents.abortIngest` — POST /documents/{documentId}/ingest/{sessionId}/abort. Abort a bulk-load session before its swap */
732
+ "abortIngest"(args: {
733
+ "documentId": string | number;
734
+ "sessionId": string | number;
735
+ body: unknown;
736
+ }): Promise<any>;
711
737
  /** `documents.addTag` — POST /documents/{documentId}/tags. Add a tag to a document */
712
738
  "addTag"(args: {
713
739
  "documentId": string | number;
@@ -785,10 +811,21 @@ export interface FunctionApi {
785
811
  "clearLinkAccess"(args: {
786
812
  "documentId": string | number;
787
813
  }): Promise<any>;
814
+ /** `documents.commitIngest` — POST /documents/{documentId}/ingest/{sessionId}/commit. Commit a bulk-load session: freeze its manifest and apply it */
815
+ "commitIngest"(args: {
816
+ "documentId": string | number;
817
+ "sessionId": string | number;
818
+ body: unknown;
819
+ }): Promise<any>;
788
820
  /** `documents.create` — POST /documents. Create a new document */
789
821
  "create"(args: {
790
822
  body: unknown;
791
823
  }): Promise<any>;
824
+ /** `documents.createIngest` — POST /documents/{documentId}/ingest. Open a bulk-load session on a large document */
825
+ "createIngest"(args: {
826
+ "documentId": string | number;
827
+ body: unknown;
828
+ }): Promise<any>;
792
829
  /** `documents.createWithAlias` — POST /documents/create-with-alias. Create a document and bind it to an alias */
793
830
  "createWithAlias"(args: {
794
831
  body: unknown;
@@ -846,6 +883,11 @@ export interface FunctionApi {
846
883
  "getImportChainStatus"(args: {
847
884
  "documentId": string | number;
848
885
  }): Promise<any>;
886
+ /** `documents.getIngest` — GET /documents/{documentId}/ingest/{sessionId}. One bulk-load session's status */
887
+ "getIngest"(args: {
888
+ "documentId": string | number;
889
+ "sessionId": string | number;
890
+ }): Promise<any>;
849
891
  /** `documents.getLinkAccess` — GET /documents/{documentId}/link-access. Get the document's "anyone with the link" access level */
850
892
  "getLinkAccess"(args: {
851
893
  "documentId": string | number;
@@ -862,6 +904,11 @@ export interface FunctionApi {
862
904
  "getSchema"(args: {
863
905
  "documentId": string | number;
864
906
  }): Promise<any>;
907
+ /** `documents.getSnapshot` — GET /documents/{documentId}/snapshots/{buildId}. One snapshot build with its verification result */
908
+ "getSnapshot"(args: {
909
+ "documentId": string | number;
910
+ "buildId": string | number;
911
+ }): Promise<any>;
865
912
  /** `documents.getStats` — GET /documents/{documentId}/stats. Document statistics (record/model/blob counts, approximate size) */
866
913
  "getStats"(args: {
867
914
  "documentId": string | number;
@@ -899,10 +946,22 @@ export interface FunctionApi {
899
946
  "listGroupPermissions"(args: {
900
947
  "documentId": string | number;
901
948
  }): Promise<any>;
949
+ /** `documents.listIngests` — GET /documents/{documentId}/ingest. List a large document's bulk-load sessions, newest first */
950
+ "listIngests"(args: {
951
+ "documentId": string | number;
952
+ "limit"?: string | number | boolean;
953
+ "cursor"?: string | number | boolean;
954
+ }): Promise<any>;
902
955
  /** `documents.listPendingInvitations` — GET /documents/{documentId}/pending-invitations. List a document's pending (deferred) invitations */
903
956
  "listPendingInvitations"(args: {
904
957
  "documentId": string | number;
905
958
  }): Promise<any>;
959
+ /** `documents.listSnapshots` — GET /documents/{documentId}/snapshots. List a large document's snapshot builds, newest first */
960
+ "listSnapshots"(args: {
961
+ "documentId": string | number;
962
+ "limit"?: string | number | boolean;
963
+ "cursor"?: string | number | boolean;
964
+ }): Promise<any>;
906
965
  /** `documents.open` — POST /documents/open. Open a document for collaboration (resolves access + alias) */
907
966
  "open"(args: {
908
967
  body: unknown;
@@ -999,6 +1058,12 @@ export interface FunctionApi {
999
1058
  "documentId": string | number;
1000
1059
  body: unknown;
1001
1060
  }): Promise<any>;
1061
+ /** `documents.uploadIngestChunk` — POST /documents/{documentId}/ingest/{sessionId}/chunks. Upload one chunk of a bulk-load session's artifact */
1062
+ "uploadIngestChunk"(args: {
1063
+ "documentId": string | number;
1064
+ "sessionId": string | number;
1065
+ body: FormData | ArrayBuffer | Uint8Array | string;
1066
+ }): Promise<any>;
1002
1067
  /** `documents.validateAccess` — POST /documents/{documentId}/validate-access. Resolve the caller's effective access level for a document */
1003
1068
  "validateAccess"(args: {
1004
1069
  "documentId": string | number;
@@ -1016,13 +1081,19 @@ export interface FunctionApi {
1016
1081
  "functionKey": string | number;
1017
1082
  body?: unknown;
1018
1083
  }): Promise<any>;
1084
+ /** `functions.invokeAsSystem` — POST /functions/{functionKey}/invoke-as-system. Invoke a server function as the system, with no caller */
1085
+ "invokeAsSystem"(args: {
1086
+ "functionKey": string | number;
1087
+ body?: unknown;
1088
+ }): Promise<any>;
1019
1089
  /** `functions.logs` — GET /functions/{functionKey}/logs. List a server function's invocation logs, newest first */
1020
1090
  "logs"(args: {
1021
1091
  "functionKey": string | number;
1022
1092
  "limit"?: string | number | boolean;
1023
1093
  "cursor"?: string | number | boolean;
1094
+ "runId"?: string | number | boolean;
1024
1095
  }): Promise<any>;
1025
- /** `functions.start` — POST /functions/{functionKey}/start. Start a task function from inside another server function */
1096
+ /** `functions.start` — POST /functions/{functionKey}/start. Start a server function as a task run */
1026
1097
  "start"(args: {
1027
1098
  "functionKey": string | number;
1028
1099
  body?: unknown;
@@ -1313,8 +1384,8 @@ export type DatabaseModels<K extends string> = K extends keyof DatabaseSchemas
1313
1384
  * The app's functions, key by key — EMPTY here and filled by the declaration
1314
1385
  * `primitive config push` writes into the config tree's `functions/`
1315
1386
  * directory (`primitive-function-types.d.ts`): each key's declared input and
1316
- * output types (from its `inputSchema` and `outputSchema`) and its mode. It
1317
- * is what types `defineFunction("<key>", handler)`.
1387
+ * output types, from its `inputSchema` and `outputSchema`. It is what types
1388
+ * `defineFunction("<key>", handler)`.
1318
1389
  */
1319
1390
  export interface FunctionSchemas {}
1320
1391
 
@@ -1342,13 +1413,6 @@ export type FunctionOutput<K extends string> = K extends keyof FunctionSchemas
1342
1413
  : any
1343
1414
  : any;
1344
1415
 
1345
- /** A declared function's mode, fixed at generation time. */
1346
- export type FunctionMode<K extends string> = K extends keyof FunctionSchemas
1347
- ? FunctionSchemas[K] extends { mode: infer M }
1348
- ? M
1349
- : "request" | "task"
1350
- : "request" | "task";
1351
-
1352
1416
  /**
1353
1417
  * The model names one database type addresses — its own, or any name at all
1354
1418
  * for a type that declares no schema (the DO is schemaless, so a grant on such
@@ -1358,9 +1422,38 @@ export type DatabaseModelNames<TModels> = [keyof TModels] extends [never]
1358
1422
  ? string
1359
1423
  : keyof TModels & string;
1360
1424
 
1425
+ /**
1426
+ * A row as the platform HANDS IT BACK: whatever the schema declared, plus the
1427
+ * record identity the platform owns (#3450).
1428
+ *
1429
+ * Every stored record has an `id`. A save that arrives without one is given a
1430
+ * ULID, a projection always keeps it, and `delete` and `patch` take nothing
1431
+ * else — so a function that queries a model and then moves one of its rows
1432
+ * needs the id of the row it just read. A schema is free to say nothing about
1433
+ * that field, and most do; the identity is the platform's, not the schema's,
1434
+ * so it is added here rather than rendered into each declaration.
1435
+ *
1436
+ * A declared `id` is REPLACED, never intersected: one declared optional would
1437
+ * stay optional on a read row it is always present on, and one declared with
1438
+ * some other type would intersect to `never` and be unusable. The platform
1439
+ * stamps a string whatever the schema says.
1440
+ *
1441
+ * An `any` row stays `any` — `DatabaseHandle<any>` is `defineQuery`'s default
1442
+ * handle, and turning its rows into an object with one known member would
1443
+ * narrow code that was deliberately left open.
1444
+ *
1445
+ * Writes are untouched: every one of them takes `Partial<TRow>`, so a row a
1446
+ * function BUILDS still needs no id.
1447
+ */
1448
+ export type PlatformRow<TRow> = 0 extends 1 & TRow
1449
+ ? TRow
1450
+ : Omit<TRow, "id"> & { id: string };
1451
+
1361
1452
  /** One model's row shape, open when the type declares no schema. */
1362
1453
  export type DatabaseModelRow<TModels, M extends string> =
1363
- M extends keyof TModels ? TModels[M] : Record<string, unknown>;
1454
+ M extends keyof TModels
1455
+ ? PlatformRow<TModels[M]>
1456
+ : Record<string, unknown> & { id: string };
1364
1457
 
1365
1458
  /**
1366
1459
  * One model of one database, scoped: the `modelName` every records call needs
@@ -1404,6 +1497,29 @@ export interface DatabaseModelHandle<TRow> {
1404
1497
  count(body?: ModelScopedBody): Promise<any>;
1405
1498
  aggregate(body: ModelScopedBody): Promise<any>;
1406
1499
  save(body: ModelScopedBody<{ id?: string; data: Partial<TRow>; [key: string]: unknown }>): Promise<any>;
1500
+ /**
1501
+ * Change some fields of ONE row — `databases.records.patch`, with this
1502
+ * handle's model bound (#3450).
1503
+ *
1504
+ * The record id is positional and the body may not carry another, for the
1505
+ * same reason it may not name another model: the runtime assigns both LAST,
1506
+ * so a body that appeared to choose either would be a lie about which row
1507
+ * moved. `condition`, `stringSets` and the rest of the operation's own keys
1508
+ * pass through untouched.
1509
+ */
1510
+ patch(
1511
+ recordId: string,
1512
+ body: ModelScopedBody<{ data: Partial<TRow>; id?: never; [key: string]: unknown }>
1513
+ ): Promise<any>;
1514
+ /**
1515
+ * Remove ONE row by the id a read handed back — `databases.records.delete`,
1516
+ * with this handle's model bound (#3450). An optional `condition` makes it
1517
+ * conditional, and travels untouched.
1518
+ */
1519
+ delete(
1520
+ recordId: string,
1521
+ body?: ModelScopedBody<{ condition?: Record<string, unknown>; id?: never }>
1522
+ ): Promise<any>;
1407
1523
  /** Several writes to THIS model in one request; the binding is per item. */
1408
1524
  batch(body: {
1409
1525
  operations: Array<ModelBatchOperation<TRow>>;
@@ -1445,10 +1561,15 @@ export type DocumentModelNames = [keyof DocumentSchemas] extends [never]
1445
1561
  ? string
1446
1562
  : keyof DocumentSchemas & string;
1447
1563
 
1448
- /** A document model's row shape, or an open record for an undeclared schema. */
1564
+ /**
1565
+ * A document model's row shape, or an open record for an undeclared schema —
1566
+ * carrying the platform's record identity either way (#3450), by the same rule
1567
+ * and for the same reason as a database row: the records DO is one
1568
+ * implementation, and `patch` and `delete` here take an id too.
1569
+ */
1449
1570
  export type DocumentModelRow<M extends string> = M extends keyof DocumentSchemas
1450
- ? DocumentSchemas[M]
1451
- : Record<string, unknown>;
1571
+ ? PlatformRow<DocumentSchemas[M]>
1572
+ : Record<string, unknown> & { id: string };
1452
1573
 
1453
1574
  /** The options a document model read takes: a filter object (or its JSON), a page size, a cursor. */
1454
1575
  export interface DocumentQueryOptions {
@@ -1461,6 +1582,26 @@ export interface DocumentQueryOptions {
1461
1582
  * One document model, bound to its document and its model name — a thin
1462
1583
  * binding over `ctx.api.documents.records.*` with the route's own shapes.
1463
1584
  */
1585
+ /**
1586
+ * One op of a document `batch` — the `documents.records.bulk` blob's shape,
1587
+ * typed to this handle's model (#3455).
1588
+ *
1589
+ * `model` is NOT a member: the handle already names it and assigns it last, so
1590
+ * a multi-model blob stays on the raw `ctx.api.documents.records.bulk`
1591
+ * operation rather than becoming a second way to say the same thing.
1592
+ */
1593
+ export type DocumentBatchOp<TRow> =
1594
+ | { action: "create" | "patch"; id: string; data: Partial<TRow>; precondition?: Record<string, unknown> }
1595
+ | { action: "delete"; id: string; precondition?: Record<string, unknown> };
1596
+
1597
+ /** What `documents.records.bulk` answers: the blob's aggregate effect. */
1598
+ export interface DocumentBulkResult {
1599
+ applied: number;
1600
+ added: Array<{ model: string; id: string }>;
1601
+ updated: Array<{ model: string; id: string }>;
1602
+ deleted: number;
1603
+ }
1604
+
1464
1605
  export interface DocumentModelHandle<TRow> {
1465
1606
  query(options?: DocumentQueryOptions): Promise<{ items: TRow[]; hasMore: boolean; nextCursor?: string }>;
1466
1607
  count(options?: { filter?: Record<string, unknown> | string }): Promise<{ count: number }>;
@@ -1468,6 +1609,15 @@ export interface DocumentModelHandle<TRow> {
1468
1609
  save(body: { id: string; data: Partial<TRow>; options?: Record<string, unknown> }): Promise<{ record: TRow }>;
1469
1610
  patch(recordId: string, body: { data: Partial<TRow>; options?: Record<string, unknown> }): Promise<{ record: TRow }>;
1470
1611
  delete(recordId: string): Promise<{ deleted: boolean }>;
1612
+ /**
1613
+ * Apply an ordered blob of this model's ops in ONE transaction — all of it
1614
+ * commits or none of it does, and connected clients see one update.
1615
+ *
1616
+ * One model's ops, by construction: every op's `model` is this handle's.
1617
+ * A blob that spans models is the raw `ctx.api.documents.records.bulk`
1618
+ * operation's job.
1619
+ */
1620
+ batch(ops: Array<DocumentBatchOp<TRow>>): Promise<DocumentBulkResult>;
1471
1621
  }
1472
1622
 
1473
1623
  /** A document, addressed once; its models are typed from `DocumentSchemas`. */
@@ -1517,15 +1667,94 @@ export interface PromptRunArgs {
1517
1667
  configId?: string;
1518
1668
  }
1519
1669
 
1520
- /** The member execute envelope, unchanged on the function path. */
1521
- export interface PromptRunResult {
1670
+ /**
1671
+ * The app's PROMPTS — EMPTY here and filled by the declaration
1672
+ * `primitive config push` writes into the config tree's `functions/` directory
1673
+ * (`primitive-prompt-types.d.ts`, from the tree's `prompts/*.toml`). A prompt
1674
+ * that declares an `outputSchema` carries `{ output: <Key>PromptOutput }`; one
1675
+ * that declares none carries `{}`, so reading `parsed.field` on it is a
1676
+ * compile error rather than a runtime `undefined`.
1677
+ */
1678
+ export interface PromptSchemas {}
1679
+
1680
+ /** The keys `ctx.prompts.run` accepts — every declared one, or `string` before a push. */
1681
+ export type PromptKeys = [keyof PromptSchemas] extends [never]
1682
+ ? string
1683
+ : keyof PromptSchemas & string;
1684
+
1685
+ /** One prompt's declared output type, or `unknown` where none is declared. */
1686
+ export type PromptOutput<K extends string> = K extends keyof PromptSchemas
1687
+ ? PromptSchemas[K] extends { output: infer O }
1688
+ ? O
1689
+ : unknown
1690
+ : unknown;
1691
+
1692
+ /**
1693
+ * Why a prompt's own answer was refused — #3330. Never set on a PROVIDER
1694
+ * failure, which is what makes "the model returned the wrong shape"
1695
+ * distinguishable from "the model failed".
1696
+ */
1697
+ export type PromptOutputErrorCode =
1698
+ | "PROMPT_OUTPUT_NOT_JSON"
1699
+ | "PROMPT_OUTPUT_SCHEMA_VIOLATION";
1700
+
1701
+ /** A prompt that declares an output shape, when the answer matched it. */
1702
+ export interface PromptRunSuccess<TParsed> {
1703
+ success: true;
1704
+ output: string;
1705
+ /** The parsed, validated answer — no caller-side `JSON.parse` (#3330). */
1706
+ parsed: TParsed;
1707
+ error?: null;
1708
+ errorCode?: null;
1709
+ metrics?: Record<string, unknown>;
1710
+ configId: string;
1711
+ }
1712
+
1713
+ /**
1714
+ * The failure arm, whichever failed. `errorCode` is set for a SHAPE failure
1715
+ * and absent for a provider one; `output` carries the text either way, which
1716
+ * is where the model's own value lives — a validation `error` names paths and
1717
+ * schema constraints only.
1718
+ */
1719
+ export interface PromptRunFailure {
1720
+ success: false;
1721
+ output: string;
1722
+ error: string | null;
1723
+ errorCode?: PromptOutputErrorCode | null;
1724
+ parsed?: undefined;
1725
+ metrics?: Record<string, unknown>;
1726
+ configId: string;
1727
+ }
1728
+
1729
+ /** The envelope for a key whose declaration says nothing about its output. */
1730
+ export interface PromptRunOpenResult {
1522
1731
  success: boolean;
1523
1732
  output: string;
1733
+ /**
1734
+ * Present when the config that ran declares `outputFormat = "json"` and the
1735
+ * text parsed. Untyped: nothing declared its shape.
1736
+ */
1737
+ parsed?: unknown;
1524
1738
  error?: string | null;
1739
+ errorCode?: PromptOutputErrorCode | null;
1525
1740
  metrics?: Record<string, unknown>;
1526
1741
  configId: string;
1527
1742
  }
1528
1743
 
1744
+ /**
1745
+ * The member execute envelope, plus what the prompt's own declaration adds.
1746
+ *
1747
+ * For a key whose `prompts/<key>.toml` declares an `outputSchema`, this is a
1748
+ * discriminated union: narrow on `success` and `parsed` is the declared type,
1749
+ * required. For every other key — and in a tree that has not pushed — it is
1750
+ * the open shape, with `parsed` optional and `unknown`.
1751
+ */
1752
+ export type PromptRunResult<K extends string = string> = K extends keyof PromptSchemas
1753
+ ? PromptSchemas[K] extends { output: infer O }
1754
+ ? PromptRunSuccess<O> | PromptRunFailure
1755
+ : PromptRunOpenResult
1756
+ : PromptRunOpenResult;
1757
+
1529
1758
  /** What a send delivered to. */
1530
1759
  export interface SendResult {
1531
1760
  /** Unique live connections the frame was sent to. Zero is a success. */
@@ -1616,9 +1845,45 @@ export interface FunctionStartResult {
1616
1845
  }
1617
1846
 
1618
1847
  export interface FunctionContext {
1619
- /** The HTTP caller, or null for a webhook, cron or database-change fire. */
1848
+ /** The HTTP caller, or null for a webhook or cron fire. */
1620
1849
  user: FunctionUser | null;
1621
1850
  trigger: FunctionTrigger;
1851
+ /**
1852
+ * Which RUNTIME is hosting this code — #3482.
1853
+ *
1854
+ * Beside `trigger`, which says which DOOR fired, and they are different
1855
+ * questions: an HTTP call is whichever runtime the caller chose
1856
+ * (`invoke` or `start`), a cron fire is always a task and a webhook
1857
+ * delivery is always a request.
1858
+ *
1859
+ * To REFUSE a runtime rather than branch on it, call
1860
+ * `assertRuntime("task")` — at module scope to refuse it outright, or in
1861
+ * the handler to refuse one call.
1862
+ */
1863
+ runtime: "request" | "task";
1864
+ /**
1865
+ * The RUN this invocation belongs to, or null — #3562.
1866
+ *
1867
+ * Set under the task runtime (every slice of the run, the first and every
1868
+ * resume) and for a webhook-fired request run, which writes a row. Null for
1869
+ * an HTTP `invoke`, which writes no run row at all.
1870
+ *
1871
+ * It is the deterministic OWNER a task run presents to re-take its own
1872
+ * lease: `ctx.api.locks.tryAcquire({ body: { key, ttlMs, owner: ctx.runId } })`
1873
+ * succeeds for the run that already holds the key, with a fresh handle.
1874
+ */
1875
+ runId: string | null;
1876
+ /**
1877
+ * The SLICE of the run this code is running in, or null — #3562.
1878
+ *
1879
+ * The same identifier `primitive functions runs` and the run-status
1880
+ * route's `slice.sliceId` report. A task run is a sequence of slices: it
1881
+ * changes when the run hibernates and wakes, which is how a handler can tell
1882
+ * a resume from a first pass.
1883
+ *
1884
+ * Null under the request runtime: a request invocation is not a slice.
1885
+ */
1886
+ sliceId: string | null;
1622
1887
  api: FunctionApi;
1623
1888
  /**
1624
1889
  * A typed handle over one database. The second argument is the database TYPE
@@ -1673,14 +1938,33 @@ export interface FunctionContext {
1673
1938
  ): Promise<FunctionStartResult>;
1674
1939
  };
1675
1940
  /**
1676
- * Run one of the app's prompts, under a `prompt:<key>` grant (#3183).
1941
+ * Run one of the app's prompts, with nothing to declare (#3183, #3279).
1677
1942
  *
1678
1943
  * The prompt's `accessRule` is not consulted — it states which MEMBERS may
1679
- * execute the prompt from a client, and the grant is what authorizes this
1680
- * call. The provider request is bounded by the invocation's own deadline.
1944
+ * execute the prompt from a client, and the invocation gate is what
1945
+ * authorized this call. The provider request is bounded by the invocation's
1946
+ * own deadline.
1947
+ *
1948
+ * TYPED BY KEY (#3330). Where `prompts/<key>.toml` declares an
1949
+ * `outputSchema`, the platform parses the answer, validates it against that
1950
+ * schema and hands it back as `parsed`, typed — so the shape is written once
1951
+ * in TOML instead of restated as a `JSON.parse` at every call site. Where
1952
+ * only the config that ran declares `outputFormat = "json"`, `parsed` is
1953
+ * present but `unknown`. Where neither is declared the envelope is exactly
1954
+ * what it has always been.
1955
+ *
1956
+ * A shape the declaration refuses is `success: false` with an `errorCode`
1957
+ * (`PROMPT_OUTPUT_NOT_JSON` or `PROMPT_OUTPUT_SCHEMA_VIOLATION`) and the
1958
+ * text still in `output`; a PROVIDER failure has `error` and no
1959
+ * `errorCode`, so the two are told apart without guessing.
1960
+ *
1961
+ * Once a tree has pushed, a key it does not declare is a compile error.
1681
1962
  */
1682
1963
  prompts: {
1683
- run(promptKey: string, args?: PromptRunArgs): Promise<PromptRunResult>;
1964
+ run<K extends PromptKeys>(
1965
+ promptKey: K,
1966
+ args?: PromptRunArgs
1967
+ ): Promise<PromptRunResult<K>>;
1684
1968
  };
1685
1969
  /**
1686
1970
  * The value of one app secret, under a `secret:<NAME>` grant (#3183).
@@ -1750,13 +2034,22 @@ export interface FunctionContext {
1750
2034
  }
1751
2035
 
1752
2036
  /**
1753
- * The durable step handle — Cloudflare Workflows' `WorkflowStep`, forwarded
1754
- * untouched, so `step.do` results are memoized and `step.sleep` hibernates.
2037
+ * The step handle. `step` is the third argument under BOTH runners, so the
2038
+ * same body runs either way — which is what `mode = "any"` is for (#3454).
1755
2039
  *
1756
- * A SYNCHRONOUS invocation gets a passthrough shim of the same shape, so a body
1757
- * written for durable mode runs either way: `do` executes the body (nothing to
1758
- * memoize into without an engine), `sleep` resolves immediately (the sync
1759
- * ceiling is seconds), and `waitForEvent` throws `STEP_NOT_AVAILABLE`.
2040
+ * Under the TASK runner it is Cloudflare Workflows' `WorkflowStep`, forwarded
2041
+ * untouched: `step.do` results are memoized and replayed, and `step.sleep`
2042
+ * hibernates.
2043
+ *
2044
+ * Under the REQUEST runner it is the platform's own, bounded by the request's
2045
+ * clock: `do` executes the body inline (there is no engine to memoize into,
2046
+ * and nothing is replayed); `sleep` and `sleepUntil` REALLY wait when the
2047
+ * remaining request budget covers the duration, and otherwise fail the
2048
+ * invocation with `errorCode: "FUNCTION_RUNTIME_REFUSED"` saying to start the
2049
+ * function as a task; `waitForEvent` fails the same way, because nothing can
2050
+ * deliver an event to an invocation that has to return now. The thrown error's
2051
+ * `name` is `STEP_NOT_AVAILABLE` in every case, so a body that catches it
2052
+ * keeps catching it.
1760
2053
  */
1761
2054
  export interface FunctionStep {
1762
2055
  do<T>(name: string, body: () => T | Promise<T>): Promise<T>;
@@ -1775,13 +2068,57 @@ export interface FunctionStep {
1775
2068
 
1776
2069
  export declare const SDK_STUB_VERSION: string;
1777
2070
 
2071
+ /**
2072
+ * Thrown by `assertRuntime` when this function is running under the other
2073
+ * runtime — #3482.
2074
+ *
2075
+ * The platform settles it as `status: "failed"` with
2076
+ * `errorCode: "FUNCTION_RUNTIME_REFUSED"`, on a request invocation's
2077
+ * envelope and on a task run's row and record alike.
2078
+ */
2079
+ export declare class FunctionRuntimeError extends Error {
2080
+ name: "FUNCTION_RUNTIME_REFUSED";
2081
+ /** The runtime this invocation is actually running under. */
2082
+ runtime: "request" | "task";
2083
+ /** The runtime the function asked for. */
2084
+ required: "request" | "task";
2085
+ }
2086
+
2087
+ /**
2088
+ * Refuse the runtime this function was not written for — #3482.
2089
+ *
2090
+ * A function is a function and the caller picks the runtime at each call, so
2091
+ * nothing in config says which one a function may run under. This is the lock,
2092
+ * and it lives in the one place that can always be right about the code:
2093
+ *
2094
+ * ```ts
2095
+ * import { assertRuntime } from "primitive-functions";
2096
+ *
2097
+ * // At module scope: this function is never runnable inside a request.
2098
+ * assertRuntime("task");
2099
+ *
2100
+ * export default async function (input, ctx, step) {
2101
+ * await step.sleep("cool-off", "1 hour");
2102
+ * return { ok: true };
2103
+ * }
2104
+ * ```
2105
+ *
2106
+ * Returns when the runtimes match; otherwise throws
2107
+ * {@link FunctionRuntimeError}, which the platform settles as
2108
+ * `status: "failed"` with `errorCode: "FUNCTION_RUNTIME_REFUSED"` and a
2109
+ * message naming both runtimes and the verb that takes the one required.
2110
+ */
2111
+ export declare function assertRuntime(required: "request" | "task"): void;
2112
+
1778
2113
  /**
1779
2114
  * The handler a function exports, typed for one declared key.
1780
2115
  *
1781
- * `step` is the third argument in BOTH modes (#3186). A task run gets the
1782
- * engine's live `WorkflowStep`; a request invocation gets a passthrough shim
1783
- * (`do` runs the body, `sleep` resolves at once, `waitForEvent` throws a
1784
- * named error), so a task-authored body can be exercised as a request.
2116
+ * `step` is the third argument under BOTH runners (#3186, #3454). A task run
2117
+ * gets the engine's live `WorkflowStep`; a request invocation gets the
2118
+ * platform's own, where `do` runs the body inline and a wait the request
2119
+ * budget cannot cover fails the invocation naming the task runner — so a
2120
+ * task-authored body can be exercised as a request, and the one place the two
2121
+ * genuinely differ says so rather than pretending.
1785
2122
  */
1786
2123
  export type FunctionHandler<TInput, TOutput> = (
1787
2124
  input: TInput,
@@ -2005,6 +2342,12 @@ export declare const stepPolicy: {
2005
2342
  database: StepPolicy;
2006
2343
  };
2007
2344
 
2345
+ // Every member the three runtime copies put on their own default export — the
2346
+ // platform's `sdk-core.ts`, the published stub and the CLI's collect stub —
2347
+ // and nothing else. A helper the module hands a default import but does not
2348
+ // declare here is one an author cannot call without the compiler refusing it
2349
+ // (#3482 CR3482-002); `tests/api/spec/function-profile.test.ts` holds the four
2350
+ // lists equal and compiles a default import against them.
2008
2351
  declare const _default: {
2009
2352
  SDK_STUB_VERSION: string;
2010
2353
  defineFunction: typeof defineFunction;
@@ -2012,5 +2355,7 @@ declare const _default: {
2012
2355
  defineMutation: typeof defineMutation;
2013
2356
  pMap: typeof pMap;
2014
2357
  ulid: typeof ulid;
2358
+ assertRuntime: typeof assertRuntime;
2359
+ FunctionRuntimeError: typeof FunctionRuntimeError;
2015
2360
  };
2016
2361
  export default _default;
package/index.js CHANGED
@@ -82,6 +82,34 @@ export const stepPolicy = {
82
82
  database: null,
83
83
  };
84
84
 
85
+ /**
86
+ * The runtime refusal's error class — #3482.
87
+ *
88
+ * Carried rather than thrown from: a function narrows a `catch` with
89
+ * `instanceof FunctionRuntimeError`, and a class that threw on construction
90
+ * would break a build that only type-checks. The platform copy is the one that
91
+ * is ever really thrown.
92
+ */
93
+ export class FunctionRuntimeError extends Error {
94
+ constructor(message, runtime, required) {
95
+ super(message);
96
+ this.name = "FUNCTION_RUNTIME_REFUSED";
97
+ this.runtime = runtime;
98
+ this.required = required;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Refuse the runtime this function was not written for — #3482.
104
+ *
105
+ * The platform copy compares the required runtime against the one the isolate
106
+ * declared. This one refuses, loudly, like every other function here: an
107
+ * assertion answered outside a sandbox would be answering about nothing.
108
+ */
109
+ export function assertRuntime() {
110
+ throw new Error(NOT_PROVIDED);
111
+ }
112
+
85
113
  export default {
86
114
  SDK_STUB_VERSION,
87
115
  defineFunction,
@@ -89,4 +117,6 @@ export default {
89
117
  defineMutation,
90
118
  pMap,
91
119
  ulid,
120
+ assertRuntime,
121
+ FunctionRuntimeError,
92
122
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primitive-functions",
3
- "version": "0.2.0-alpha.7",
3
+ "version": "0.2.0-alpha.9",
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",