palbase 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/ai/index.js +2 -14
  2. package/dist/backend/engine.js +2 -2
  3. package/dist/backend/env.js +1 -1
  4. package/dist/backend/index.js +9 -9
  5. package/dist/backend/stack.js +1 -1
  6. package/dist/backend/test.js +2 -2
  7. package/dist/chunks/5VTGMNPL.js +20 -0
  8. package/dist/chunks/{SXH5AIQX.js → BO5KR2EQ.js} +2 -2
  9. package/dist/chunks/{AYY4J3B2.js → BRBCWPR3.js} +1 -1
  10. package/dist/chunks/DB3SIBKB.js +2 -0
  11. package/dist/chunks/EGNEXFTZ.js +2 -0
  12. package/dist/chunks/{VP7V7YRH.js → FZ3MQOVA.js} +2 -2
  13. package/dist/chunks/{A3CDCPXX.js → JSJVV43D.js} +1 -1
  14. package/dist/chunks/K6RRNI23.js +4 -0
  15. package/dist/chunks/{SUX2BJG4.js → KDWDPSL2.js} +1 -1
  16. package/dist/chunks/P4Z5E6ZI.js +2 -0
  17. package/dist/chunks/PDOHT46N.js +2 -0
  18. package/dist/chunks/PGPQGKH2.js +18 -0
  19. package/dist/chunks/{DRTZ33XF.js → RBA6V3UO.js} +2 -2
  20. package/dist/chunks/{KLANM6M6.js → RO5Y5YU4.js} +1 -1
  21. package/dist/chunks/{53FS342F.js → TUGRIOFX.js} +2 -2
  22. package/dist/chunks/WEI32SAJ.js +13 -0
  23. package/dist/chunks/{GVFZM4BI.js → WINBISC2.js} +3 -3
  24. package/dist/chunks/Z5JVQNAZ.js +2 -0
  25. package/dist/cli.js +140 -139
  26. package/dist/refuse/ai.js +1 -1
  27. package/dist/refuse/backend.js +1 -1
  28. package/dist/refuse/server.js +1 -1
  29. package/dist/server/index.js +2 -2
  30. package/dist/server/testing.js +2 -2
  31. package/dist/web/index.js +2 -2
  32. package/dist/web/palbe.js +2 -2
  33. package/dist/web/paywall.js +2 -2
  34. package/dist/web/ssr.js +2 -2
  35. package/dist/web/vite.js +2 -2
  36. package/package.json +2 -2
  37. package/types/ai/src/ai.d.ts +46 -19
  38. package/types/ai/src/chats.d.ts +478 -18
  39. package/types/ai/src/index.d.ts +4 -4
  40. package/types/ai/src/tools.d.ts +161 -29
  41. package/types/backend/src/bootstrap.d.ts +8 -0
  42. package/types/backend/src/container.d.ts +11 -2
  43. package/types/backend/src/database.d.ts +17 -2
  44. package/types/backend/src/engine.d.ts +3 -4
  45. package/types/backend/src/gateways.d.ts +269 -0
  46. package/types/backend/src/index.d.ts +3 -2
  47. package/types/backend/src/openapi.d.ts +3 -6
  48. package/types/backend/src/runtime.d.ts +51 -2
  49. package/types/backend/src/testing/unit.d.ts +3 -1
  50. package/types/internal/src/chat.d.ts +168 -35
  51. package/types/internal/src/modules/ai.d.ts +1826 -545
  52. package/types/internal/src/modules/auth.d.ts +14 -2
  53. package/types/internal/src/modules/events.d.ts +2 -2
  54. package/types/internal/src/modules/machines.d.ts +57 -1
  55. package/types/internal/src/modules/notify.d.ts +6 -6
  56. package/types/internal/src/modules/purchases.d.ts +357 -13
  57. package/types/internal/src/modules/realtime.d.ts +45 -1
  58. package/types/internal/src/modules/stores.d.ts +259 -22
  59. package/types/internal/src/transport.d.ts +5 -0
  60. package/types/server/src/auth/jwt.d.ts +1 -6
  61. package/types/web/src/auth.d.ts +15 -4
  62. package/types/web/src/backends.d.ts +20 -13
  63. package/types/web/src/chat.d.ts +329 -0
  64. package/types/web/src/client.d.ts +28 -3
  65. package/types/web/src/gateways.d.ts +90 -0
  66. package/types/web/src/index.d.ts +5 -4
  67. package/types/web/src/session.d.ts +11 -1
  68. package/types/web/src/socket.d.ts +18 -2
  69. package/types/web/src/voice.d.ts +36 -0
  70. package/dist/chunks/2RG3I7LX.js +0 -6
  71. package/dist/chunks/A76UZBAF.js +0 -2
  72. package/dist/chunks/AD5LBZAB.js +0 -4
  73. package/dist/chunks/DRGYTL6T.js +0 -20
  74. package/dist/chunks/IVEAUFGE.js +0 -13
  75. package/dist/chunks/IYCW6PWG.js +0 -2
  76. package/dist/chunks/KL7FIRII.js +0 -2
  77. package/dist/chunks/U4OZ4HOF.js +0 -2
  78. package/types/backend/src/channels.d.ts +0 -81
  79. package/types/backend/src/rooms.d.ts +0 -178
  80. package/types/web/src/ai.d.ts +0 -224
  81. package/types/web/src/rooms.d.ts +0 -61
@@ -25,12 +25,58 @@ export interface ChatContext {
25
25
  metadata: Record<string, string>;
26
26
  /** Ended when the platform stops waiting for the answer. */
27
27
  signal: AbortSignal;
28
+ /**
29
+ * The app's instance of a provider — a class a module of the app lists in
30
+ * its providers, built once by its container with its constructor's
31
+ * dependencies — as a constructor's parameter of that type is given it:
32
+ * ctx.inject(Orders). The app that serves the call gives it; a class no
33
+ * module provides is refused, by name.
34
+ */
35
+ inject<T extends object>(provider: abstract new (...args: never[]) => T): T;
28
36
  }
29
37
  export interface ToolContext extends ChatContext {
30
38
  /** The call's id: the same call is the same id, whatever copy of the machine it reaches. */
31
39
  callId: string;
40
+ /** The turn the call is made in: its own response's id — a subagent's call's too. */
41
+ turnId: string;
42
+ /**
43
+ * The run that made the call: the agent's own, or a subagent's at work. A
44
+ * tool that answers later — it asks a person, and gives their answer to
45
+ * whoever asked — answers that run: chats.sendToSubagent(chatId, runId, …).
46
+ */
47
+ runId: string;
48
+ /** The subagent that made the call, by its name; null: the agent itself. */
49
+ subagent: string | null;
50
+ }
51
+ /** The message that opened a turn, as the backend that defines its agent is told of it. */
52
+ export interface TurnMessage {
53
+ id: string;
54
+ clientId: string;
55
+ /** The member who wrote it; null: the project's server. */
56
+ author: string | null;
57
+ authorName: string;
58
+ text: string;
59
+ delivery: 'next' | 'now' | (string & {});
60
+ /**
61
+ * Its writer's labels for it (chats.send({ metadata })) — what an app's
62
+ * choice for the turn rides on: a model, an effort, the files it names. A
63
+ * member may write them: the caller's word, not an authority.
64
+ */
65
+ metadata: Record<string, string>;
66
+ }
67
+ /** What the backend that defines an agent is told of the turn: the chat's context, and what opened the turn. */
68
+ export interface AgentContext extends ChatContext {
69
+ /** The message that opened the turn; null: a turn of no message (the chat's voice, its goal). */
70
+ message: TurnMessage | null;
71
+ /**
72
+ * The chat's goal the agent is defined for (chats.goal.set): every turn the
73
+ * goal opens is the agent defined now. Null: a turn of a message.
74
+ */
75
+ goal: {
76
+ objective: string;
77
+ rubric: string;
78
+ } | null;
32
79
  }
33
- export type AgentContext = ChatContext;
34
80
  export interface BackendToolOptions<S extends StandardSchema> {
35
81
  /** What the agent calls it by: letters, digits, _ and -, starting with a letter, at most 64. */
36
82
  name: string;
@@ -43,9 +89,11 @@ export interface BackendToolOptions<S extends StandardSchema> {
43
89
  * (`{ mediaType, bytes }`), or a list of them — 4 MiB at most in all. A
44
90
  * throw is the tool's failure: a ToolError's message (or the framework's
45
91
  * own refusals', NotFound('…')) is told to the agent, anything else as
46
- * "The tool failed".
92
+ * "The tool failed". A tool answers within 110 seconds; one whose work
93
+ * takes longer returns ai.deferred() at once — a chat's call waits, and the
94
+ * project's server answers it later with chats.answer(ctx.chatId, ctx.callId, output).
47
95
  */
48
- run(input: Infer<S>, ctx: ToolContext): ToolResult | Promise<ToolResult>;
96
+ run(input: Infer<S>, ctx: ToolContext): ToolResult | DeferredAnswer | Promise<ToolResult | DeferredAnswer>;
49
97
  /** Every call waits for a person's yes (a member of the chat, or the server). */
50
98
  approval?: boolean;
51
99
  /** The project's machine it runs on (default backend). */
@@ -60,12 +108,34 @@ export interface BackendTool<S extends StandardSchema = StandardSchema> {
60
108
  readonly parameters: Record<string, unknown>;
61
109
  readonly approval: boolean;
62
110
  readonly machine?: string;
63
- /** The run, its input checked first: what the framework answers the platform's call with. */
64
- call(input: unknown, ctx: ToolContext): Promise<{
65
- output: ToolContent[];
66
- is_error: boolean;
67
- }>;
111
+ /** The run, its input checked first: what the framework answers the platform's call with — an answer, or one to come (deferred). */
112
+ call(input: unknown, ctx: ToolContext): Promise<ToolAnswer>;
68
113
  }
114
+ /** A backend tool's answer as the platform takes it: now (output), or later — the project's server gives it (chats.answer). */
115
+ export type ToolAnswer = {
116
+ output: ToolContent[];
117
+ is_error: boolean;
118
+ } | {
119
+ deferred: true;
120
+ expires_in_seconds?: number;
121
+ };
122
+ declare const DEFERRED: unique symbol;
123
+ /** What a backend tool's run returns when its answer comes later: ai.deferred(). */
124
+ export interface DeferredAnswer {
125
+ readonly [DEFERRED]: true;
126
+ /** How long the call waits for its answer: a minute to a day; left out, an hour. */
127
+ readonly expiresInSeconds?: number;
128
+ }
129
+ /**
130
+ * The answer comes later: a chat's call waits for it — the turn too, as for
131
+ * an app's tool — and the project's server gives it with chats.answer(chatId,
132
+ * callId, output) within expiresInSeconds (a minute to a day; an hour). A
133
+ * call nobody answers by then is told to the agent as unanswered; no member
134
+ * answers it. A one-shot answer (ai.ask with tools) waits for no call.
135
+ */
136
+ export declare function deferred(options?: {
137
+ expiresInSeconds?: number;
138
+ }): DeferredAnswer;
69
139
  export interface ClientToolOptions<S extends StandardSchema> {
70
140
  name: string;
71
141
  description: string;
@@ -73,7 +143,7 @@ export interface ClientToolOptions<S extends StandardSchema> {
73
143
  input?: S;
74
144
  approval?: boolean;
75
145
  }
76
- /** A tool the app's code answers (pb.ai.chat(id).tools({ name: fn })): the turn waits for its answer. */
146
+ /** A tool the app answers — through its gateway, whose backend gives the answer (chats.answer): the turn waits for it. */
77
147
  export interface ClientTool {
78
148
  readonly source: 'client';
79
149
  readonly name: string;
@@ -94,7 +164,23 @@ export interface McpToolRef {
94
164
  only?: string[];
95
165
  approval?: boolean;
96
166
  }
97
- export type Tool = BackendTool<StandardSchema> | ClientTool | HttpsToolRef | McpToolRef;
167
+ /** One of the platform's own tools, by name. */
168
+ export type PlatformToolName = 'web.search' | 'web.fetch' | 'workspace.fetch_archive';
169
+ /**
170
+ * One of the platform's own tools: web.search (the web searched), web.fetch
171
+ * (a page of it read), workspace.fetch_archive (an archive at a URL unpacked
172
+ * into the chat's workspace — a workspace's chat has it already). The
173
+ * agent's alone: its subagents work with the agent's; a one-shot answer has none.
174
+ */
175
+ export interface PlatformToolRef {
176
+ source: 'platform';
177
+ name: PlatformToolName;
178
+ }
179
+ export type Tool = BackendTool<StandardSchema> | ClientTool | HttpsToolRef | McpToolRef | PlatformToolRef;
180
+ /** How hard a model thinks; which rungs a model takes is its catalogue entry's (ai.models(): chat[].efforts). */
181
+ export type Effort = NonNullable<AgentDefinition['effort']>;
182
+ /** Whether a model's reasoning is shown in the chat's stream (thinking parts) or not asked for at all. */
183
+ export type Thinking = NonNullable<AgentDefinition['thinking']>;
98
184
  export interface SubagentDefinition {
99
185
  /** Unique among the agent's subagents. */
100
186
  name: string;
@@ -105,6 +191,12 @@ export interface SubagentDefinition {
105
191
  tools?: Tool[];
106
192
  /** The turn fails if this subagent's part fails. */
107
193
  required?: boolean;
194
+ /** Left out: the agent's, when it runs on the agent's model (another model's rungs mean other things). */
195
+ effort?: Effort;
196
+ /** The window, in tokens, it works in. Left out: the agent's, when it runs on the agent's model. */
197
+ contextWindow?: number;
198
+ /** Left out: the agent's. Its reasoning is never in the chat's stream: only the turn's own is. */
199
+ thinking?: Thinking;
108
200
  }
109
201
  /** What an agent is for a turn. */
110
202
  export interface AgentDefinitionInput {
@@ -114,27 +206,55 @@ export interface AgentDefinitionInput {
114
206
  tools?: Tool[];
115
207
  /** 16 at most. */
116
208
  subagents?: SubagentDefinition[];
117
- /** How many tool calls a turn makes at most (1–200): past it the agent is told it has no tools left and answers. */
209
+ /**
210
+ * How many tool calls a turn makes at most (1–10 000, the engine's own bound): past it the agent is told it has no
211
+ * tools left and answers. What a turn spends is its budget's to bound (chats.send({ budget })), not this.
212
+ */
118
213
  maxToolCalls?: number;
119
- /** A named agent's: the app's users start chats with it themselves (when the project lets them). Left out, the server's alone. */
120
- clients?: boolean;
214
+ /** How hard the model thinks (none … max). Left out: the platform's. */
215
+ effort?: Effort;
216
+ /**
217
+ * The window, in tokens, the turn works in: the model's standard one unless said; more is billed as its provider
218
+ * bills it (ai.models(): chat[].context_window and max_input_tokens).
219
+ */
220
+ contextWindow?: number;
221
+ /** visible: the model's reasoning is in the chat's stream, as thinking parts (the turn's own); hidden: not asked for. Left out: the platform's. */
222
+ thinking?: Thinking;
223
+ /** The language of the person the turn works for, a BCP 47 tag (tr, en-GB): the model writes to them in it on every step, and so do its subagents. */
224
+ language?: string;
121
225
  }
122
- /** What a backend's agent is for one turn: whether users reach it is the named agent's, said when it is put. */
123
- export type TurnDefinition = Omit<AgentDefinitionInput, 'clients'>;
226
+ /** What a backend's agent is for one turn. */
227
+ export type TurnDefinition = AgentDefinitionInput;
124
228
  /** An agent the backend defines at each turn's start: put with ai.agents.put(agent), its chats' turns ask its machine. */
125
229
  export interface BackendAgent {
126
230
  readonly kind: 'agent';
127
231
  readonly name: string;
128
232
  /** The project's machine that defines it (default backend). */
129
233
  readonly machine?: string;
130
- /** The app's users start chats with it themselves (put with it). */
131
- readonly clients: boolean;
132
234
  /** The definition for a turn, said as the platform takes it, with the backend tools it hands out. */
133
235
  resolve(ctx: AgentContext): Promise<{
134
236
  definition: AgentDefinition;
135
237
  tools: BackendTool[];
136
238
  }>;
137
239
  }
240
+ /** What one clone or one push of a chat's workspace asks a repository's token with: an ai.repo's token, ai.repoTokens' give. */
241
+ export interface RepoTokenContext {
242
+ /** The repository's name in the project (ai.repos.put). */
243
+ repo: string;
244
+ /** The GitHub repository, owner/name. */
245
+ repository: string;
246
+ /** Ended when the platform stops waiting for the token. */
247
+ signal: AbortSignal;
248
+ /**
249
+ * The app's instance of a provider, as a tool's run is given it
250
+ * (ChatContext.inject): a token a service of the app's mints — its GitHub
251
+ * App's, its sealed credentials' — from the container of the app that
252
+ * serves the ask. A class no module provides is refused, by name.
253
+ */
254
+ inject<T extends object>(provider: abstract new (...args: never[]) => T): T;
255
+ }
256
+ /** What gives a repository's token when no ai.repo of its name was made: ai.repoTokens(give). */
257
+ export type RepoTokens = (ctx: RepoTokenContext) => string | null | undefined | Promise<string | null | undefined>;
138
258
  /** A repository a chat's workspace works in, whose tokens the backend gives (ai.repo). */
139
259
  export interface BackendRepo {
140
260
  readonly kind: 'repo';
@@ -145,18 +265,16 @@ export interface BackendRepo {
145
265
  /** The project's machine that gives its tokens (default backend). */
146
266
  readonly machine?: string;
147
267
  /** A token for one clone or one push of it: asked at each — a GitHub App's installation token, minted then. */
148
- token(ctx: {
149
- repo: string;
150
- repository: string;
151
- signal: AbortSignal;
152
- }): Promise<string>;
268
+ token(ctx: RepoTokenContext): Promise<string>;
153
269
  }
154
270
  /** What this process declared, for the framework to serve. */
155
271
  export interface DeclaredAI {
156
272
  tools: Map<string, BackendTool>;
157
273
  agents: Map<string, BackendAgent>;
158
274
  repos: Map<string, BackendRepo>;
159
- /** Names declared twice (tool:<name>, agent:<name>): the framework refuses to start with one. */
275
+ /** The tokens of a repository put by name at run time, which no ai.repo of this process names. */
276
+ repoTokens?: RepoTokens;
277
+ /** Names declared twice (tool:<name>, agent:<name>, repoTokens): the framework refuses to start with one. */
160
278
  twice: Set<string>;
161
279
  }
162
280
  /**
@@ -185,18 +303,32 @@ export declare function backendRepo(options: {
185
303
  repository: string;
186
304
  defaultBranch?: string;
187
305
  machine?: string;
188
- token: (ctx: {
189
- repo: string;
190
- repository: string;
191
- signal: AbortSignal;
192
- }) => string | Promise<string>;
306
+ token: (ctx: RepoTokenContext) => string | Promise<string>;
193
307
  }): BackendRepo;
308
+ /**
309
+ * The tokens of the project's repositories put by name at run time
310
+ * (ai.repos.put(name, { repository, backend })) — one a product makes for
311
+ * itself — which no ai.repo of this backend names: each clone and each push
312
+ * of a chat's workspace asks this machine, and give answers with a token for
313
+ * that one operation, or null for a repository it gives none for (the
314
+ * workspace is refused it). An ai.repo of the name answers for its own
315
+ * repository first. Given once, at a module's top level.
316
+ */
317
+ export declare function repoTokens(give: RepoTokens): void;
194
318
  export declare function backendAgent(name: string, define: (ctx: AgentContext) => TurnDefinition | Promise<TurnDefinition>, options?: {
195
319
  machine?: string;
196
- clients?: boolean;
197
320
  }): BackendAgent;
321
+ /** A backend's agent (an ai.agent's value) — its name and machine — or undefined for anything else. */
322
+ export declare function backendAgentOf(v: unknown): {
323
+ readonly name: string;
324
+ readonly machine?: string;
325
+ } | undefined;
198
326
  /** A tool as the platform takes it (ToolRef). */
199
327
  export declare function toolRef(t: Tool, what: string): ToolRef;
328
+ /** The most tool calls a turn or a one-shot answer makes: the engine's own bound. */
329
+ export declare const MAX_TOOL_CALLS = 10000;
330
+ /** A definition's bound on its tool calls, refused here as the module would refuse it. */
331
+ export declare function maxToolCallsOf(n: unknown, what: string): number;
200
332
  /** A definition as the platform takes it (AgentDefinition). */
201
333
  export declare function definitionOf(d: AgentDefinitionInput, what: string): AgentDefinition;
202
334
  export {};
@@ -70,3 +70,11 @@ export declare class MigrationRunEnded extends Error {
70
70
  readonly code: number;
71
71
  constructor(code: number);
72
72
  }
73
+ /**
74
+ * The app's CORS joined to the origins of its project's web apps, as the
75
+ * platform starts it with them (PALBASE_WEB_ORIGINS, comma-separated): a
76
+ * browser on one of them may call it without the app naming it. Any origin
77
+ * ("*") stays any. An origin CORS cannot hold (an address in brackets) is
78
+ * left out, and said.
79
+ */
80
+ export declare function withWebOrigins(own: CorsOptions | undefined, given: string | undefined, warn?: (message: string) => void): CorsOptions | undefined;
@@ -14,6 +14,8 @@ export declare class Container {
14
14
  private readonly owner;
15
15
  private readonly defs;
16
16
  private readonly instances;
17
+ /** The classes the modules list as providers (not controllers): what ctx.inject may be given. */
18
+ private readonly providers;
17
19
  /** Construction order: dependencies before their dependents. */
18
20
  readonly order: object[];
19
21
  readonly controllers: {
@@ -28,8 +30,8 @@ export declare class Container {
28
30
  readonly cronProviders: Ctor[];
29
31
  /** The providers that are @Task classes: the platform runs them as their tasks come due. */
30
32
  readonly taskProviders: Ctor[];
31
- /** The providers that are @Room classes. */
32
- readonly rooms: Ctor[];
33
+ /** The providers that are @Gateway classes. */
34
+ readonly gateways: Ctor[];
33
35
  constructor(modules: Function[]);
34
36
  private collect;
35
37
  private validate;
@@ -41,6 +43,13 @@ export declare class Container {
41
43
  * a route's @Resolve, by its controller — under a constructor's rules.
42
44
  */
43
45
  dependencyOf<T extends object>(consumer: abstract new (...args: never[]) => object, dep: abstract new (...args: never[]) => T, at: string): T;
46
+ /**
47
+ * The built instance of a provider, asked for by code no module owns — a
48
+ * tool's run, an agent's function (ctx.inject) — so any module's provider,
49
+ * exported or not: the class itself, or the one provider that extends it,
50
+ * as a constructor's parameter of that type finds it. `at` says who asked.
51
+ */
52
+ provider<T extends object>(cls: abstract new (...args: never[]) => T, at: string): T;
44
53
  private reachable;
45
54
  private target;
46
55
  /** onStart() of every instance, in construction order; a throw stops the boot after the started ones are shut down. */
@@ -54,7 +54,7 @@ export interface DatabaseConfig {
54
54
  * nothing — `palbase db:plan` and `db:status` ask the app's own Palgres what a
55
55
  * start would do and what the database's ledger says.
56
56
  */
57
- export type MigrateMode = 'start' | 'complete' | 'rollback' | 'plan' | 'status';
57
+ export type MigrateMode = 'start' | 'complete' | 'rollback' | 'plan' | 'status' | 'types';
58
58
  /** What the deploy asked of this run: a migration step, or nothing (serve). */
59
59
  export declare function migrateModeOf(env: Record<string, string | undefined>): MigrateMode | undefined;
60
60
  /**
@@ -106,9 +106,24 @@ export type MigrationExit = 0 | 1 | 75;
106
106
  * given to such a machine with its migrations on is refused as its release
107
107
  * starts, and the release's rollback then ends — the machine is not held by it.
108
108
  */
109
- export declare function migrateWithoutDatabase(mode: MigrateMode): MigrationExit;
109
+ export declare function migrateWithoutDatabase(mode: MigrateMode): Promise<MigrationExit>;
110
+ /**
111
+ * The app's tables as types (palbase db:types): each table's row, insert, unique keys and relations both ways —
112
+ * across its schema files, which a schema's own type cannot see (TablesOf) — as the block the CLI writes into
113
+ * palbase/palbase-env.d.ts. No database is asked: the schema files say it all.
114
+ */
115
+ export declare function writeTypes(config: DatabaseConfig | undefined, out?: AnswerOut): Promise<MigrationExit>;
110
116
  /** The deploy's migration step. */
111
117
  export declare function runMigration(config: DatabaseConfig, env: Record<string, string | undefined>, mode: MigrateMode): Promise<MigrationExit>;
118
+ /** Where the step's answer is written: the standard output's stream, done once what it was given is out. */
119
+ export type AnswerOut = (text: string, done: () => void) => void;
120
+ /**
121
+ * The step's answer, as one line on stdout the CLI reads: {"palbase_migrate": mode, "result": …} — out, and every
122
+ * log line before it, when this resolves: the run exits right after, and on a pipe what the stream still held went
123
+ * with the process (a large plan's line was cut at what the pipe took: the CLI read "Unterminated string in JSON at
124
+ * position 131072"). One stream, in order: a log line is never cut by it.
125
+ */
126
+ export declare function said(mode: MigrateMode, result: unknown, out?: AnswerOut): Promise<void>;
112
127
  export interface OpenDatabase {
113
128
  model: Schema;
114
129
  binding: Binding;
@@ -1,7 +1,6 @@
1
1
  import { type PalbaseServer, type TokenUser, type TokenVerifier, type WebhookEvent } from '../../server/src/index.js';
2
2
  import { type AuthSpec, type RateLimitConfig } from './decorators.js';
3
3
  import { type ContractOptions } from './openapi.js';
4
- import { type ChannelsDef } from './channels.js';
5
4
  import { type StringsTable } from './i18n.js';
6
5
  import type { RequestTelemetry } from './requests.js';
7
6
  /** The signed-in user, as @User() gives it. */
@@ -63,8 +62,6 @@ export interface AppOptions {
63
62
  cors?: CorsOptions;
64
63
  /** The framework's own handlers of the platform's events (user.deleted → forget), run before the app's @On. */
65
64
  events?: Record<string, ((event: WebhookEvent) => Promise<unknown>)[]>;
66
- /** The app's channels; the ones defineChannels declared in this process unless given (a test's own). */
67
- channels?: ChannelsDef;
68
65
  /** The app's database: each request (and each delivery) one transaction, as its caller or as the service role. */
69
66
  database?: {
70
67
  app: RequestDatabases;
@@ -104,7 +101,7 @@ export interface AppOptions {
104
101
  * never to be done (TaskFailure, or a payload its schema refuses), or the
105
102
  * failure the platform tries again;
106
103
  * - its @Upload routes' two steps, from storage (upload.ts), and its
107
- * @Room classes' joins, messages and moments, from realtime (rooms.ts):
104
+ * @Gateway classes' joins, messages and moments, from realtime (gateways.ts):
108
105
  * platform-calls.ts;
109
106
  * - its ai tools and agents (@palbase/ai's ai.tool and ai.agent), from the
110
107
  * ai module: POST /_palbase/ai/tools runs the tool an agent called (its
@@ -141,3 +138,5 @@ export interface App {
141
138
  }
142
139
  export declare const MAX_BODY_BYTES: number;
143
140
  export declare function createApp(opts: AppOptions): App;
141
+ /** An origin CORS holds: https://host[:port], http only for localhost, no path. */
142
+ export declare function corsOrigin(origin: string): boolean;
@@ -0,0 +1,269 @@
1
+ import type { realtime } from '../../internal/src/modules/index.js';
2
+ import { type StandardSchemaV1 } from './decorators.js';
3
+ import { type SignedUser } from './platform-calls.js';
4
+ export declare const GATEWAY: unique symbol;
5
+ export declare const GATEWAY_HOOKS: unique symbol;
6
+ /** The moments a gateway's methods are told of. */
7
+ export type GatewayHook = 'authorize' | 'first' | 'connect' | 'disconnect' | 'empty';
8
+ export interface GatewayOptions {
9
+ /**
10
+ * Who may join, decided by realtime: 'owner' — the pattern's one {param}
11
+ * is the joining user's id; 'public' — any signed-in user. Left out, an
12
+ * @Authorize method decides.
13
+ */
14
+ access?: 'owner' | 'public';
15
+ /**
16
+ * Members broadcast to each other, as realtime carries it — a gateway of
17
+ * no method only: one with methods takes its members' messages itself
18
+ * (@Subscribe).
19
+ */
20
+ publish?: boolean;
21
+ /**
22
+ * Keeps what it sends: true a day, or { keepSeconds } (1 to 604800). A
23
+ * member that was away is sent what it missed, in order, once — a chat's
24
+ * stream that a phone walking out of range does not lose.
25
+ */
26
+ durable?: boolean | {
27
+ keepSeconds: number;
28
+ };
29
+ /**
30
+ * How long it is held after its last member left before it is empty
31
+ * (@OnEmpty), in milliseconds: 60000 unless said.
32
+ */
33
+ graceMs?: number;
34
+ /**
35
+ * It carries a chat of the project's agents (ctx.pipe(Palbase.ai.chats.events(id))):
36
+ * its event `chat` is the platform's — the chat's parts — and every
37
+ * generated client holds the chat's state for its view (messages, the
38
+ * answer as it is written, tools, approvals). Kept a day unless durable
39
+ * says how long: a member that comes later, or was away, has the chat.
40
+ */
41
+ chat?: boolean;
42
+ }
43
+ /**
44
+ * Something the platform writes into a gateway's channel itself, the backend
45
+ * not in it: a chat's stream (Palbase.ai.chats.events(id)).
46
+ */
47
+ export interface PipeSource {
48
+ pipe(topic: string, options?: {
49
+ signal?: AbortSignal;
50
+ }): Promise<void>;
51
+ }
52
+ /** The platform's event of a chat-carrying gateway: one part of the chat (its type, the journal's number, its data). */
53
+ export declare const ChatPart: StandardSchemaV1<{
54
+ type: string;
55
+ id?: number;
56
+ data: Record<string, unknown>;
57
+ }>;
58
+ /**
59
+ * Thrown by a @Subscribe: the member's message is refused, with this reason —
60
+ * the client's GatewayError.reason, a word the app chose for its own app to
61
+ * act on ('not_your_turn', 'too_many_messages'). What the method wrote is
62
+ * rolled back. Anything else a method throws is the app's log's: its words
63
+ * could carry anything, and the member is told handler_failed.
64
+ */
65
+ export declare class GatewayRefusal extends Error {
66
+ readonly reason: string;
67
+ constructor(reason: string);
68
+ }
69
+ /** What a gateway class declares its events with: `static readonly events = { name: schema }`. */
70
+ export interface GatewayEvents {
71
+ readonly events?: Readonly<Record<string, StandardSchemaV1>>;
72
+ }
73
+ /** A gateway as its decorators declare it. */
74
+ export interface GatewayDeclaration {
75
+ /** The class's name: its client's, in every generated client. */
76
+ name: string;
77
+ pattern: string;
78
+ access?: 'owner' | 'public';
79
+ publish: boolean;
80
+ /** How long it keeps what it sends, in seconds: 0 keeps nothing. */
81
+ logKeepSeconds: number;
82
+ /** It carries a chat (GatewayOptions.chat): its event `chat` is the chat's parts. */
83
+ chat: boolean;
84
+ events: Record<string, StandardSchemaV1>;
85
+ graceMs: number;
86
+ /** Each moment, the method told of it. */
87
+ hooks: Partial<Record<GatewayHook, string>>;
88
+ /** Each message a member may send: its method, its payload's schema, and its ack's when said — an object of no prototype, whatever the names. */
89
+ messages: Record<string, {
90
+ fn: string;
91
+ schema: StandardSchemaV1;
92
+ reply?: StandardSchemaV1;
93
+ }>;
94
+ }
95
+ /** Whether the machine serves it — a method of it is told of something — or realtime decides it alone. */
96
+ export declare const servedByTheApp: (g: GatewayDeclaration) => boolean;
97
+ /** Who a member is, to a gateway's methods. */
98
+ export interface GatewayUser {
99
+ /** The user's id; "" in @OnFirst and @OnEmpty, which no one in particular caused. */
100
+ id: string;
101
+ }
102
+ /** What @Authorize is given: who asks to join, and the channel's parameters. */
103
+ export interface GatewayAuthorizeContext {
104
+ user: GatewayUser;
105
+ params: Readonly<Record<string, string>>;
106
+ }
107
+ /**
108
+ * What @Authorize answers: true lets the user in to hear and to send; an
109
+ * object says which — { read: false } is no way in, { write: false } a
110
+ * listener who may not send; null, undefined or false keep the user out. So
111
+ * does any other answer, and a method that throws: only a grant lets anyone
112
+ * in, and the app's log says what was answered instead.
113
+ */
114
+ export type GatewayGrant = boolean | null | undefined | {
115
+ read?: boolean;
116
+ write?: boolean;
117
+ };
118
+ /** What a gateway's moments are given (@OnFirst, @OnConnect, @OnDisconnect, @OnEmpty). */
119
+ export interface GatewayContext {
120
+ /** The channel: "chat:42". */
121
+ channel: string;
122
+ /** The pattern's parameters: { chatId: "42" }. */
123
+ params: Readonly<Record<string, string>>;
124
+ /**
125
+ * The channel's own: aborted when it empties (before @OnEmpty runs), or
126
+ * when owner.release() gives it up — what @OnFirst started stops on it.
127
+ */
128
+ signal: AbortSignal;
129
+ /** Its members now, across realtime's replicas; 0 in a @Subscribe. */
130
+ count: number;
131
+ user: GatewayUser;
132
+ /**
133
+ * An event to the channel's members — in @OnConnect, to the member who
134
+ * joined alone. Sent in the order emitted, and never thrown: what could
135
+ * not be sent is in the app's log.
136
+ */
137
+ emit(event: string, payload?: unknown): void;
138
+ /**
139
+ * The platform writes `source` into this channel from now on — a chat's
140
+ * stream: `ctx.pipe(Palbase.ai.chats.events(chatId))`. The backend is not
141
+ * in the stream after; the gateway's members hear it.
142
+ */
143
+ pipe(source: PipeSource): Promise<void>;
144
+ owner: {
145
+ /** Runs `work` past the method, until the channel's signal aborts: an upstream's loop. Its failure is logged, never the app's crash. */
146
+ hold(work: (signal: AbortSignal) => unknown): void;
147
+ /** Gives the channel up now: its signal aborts, and the next member to come finds it unheld (@OnFirst again). */
148
+ release(): void;
149
+ };
150
+ }
151
+ /** What a @Subscribe is given: the channel's context, the member, and the payload as its schema made it. */
152
+ export interface GatewayMessageContext<P = unknown> extends GatewayContext {
153
+ payload: P;
154
+ }
155
+ /** v2 held a room a minute after its last member: twice the slowest client's heartbeat, and margin. */
156
+ export declare const DEFAULT_GRACE_MS = 60000;
157
+ /** A durable gateway keeps what it sends a day unless it says; a week at most. */
158
+ export declare const DEFAULT_KEEP_SECONDS = 86400;
159
+ export declare const MAX_KEEP_SECONDS = 604800;
160
+ /** The most a @Subscribe's ack carries (realtime's roomReplyMax). */
161
+ export declare const MAX_REPLY_BYTES: number;
162
+ /**
163
+ * Why `pattern` is no gateway's, or undefined. A pattern is a channel's: ':'
164
+ * segments, a "{name}" segment taking exactly one — and realtime takes a
165
+ * pattern it cannot decide for no channel at all, silently: the gateway
166
+ * would compile, ship and never be joined. So everything it would not take is
167
+ * refused here, by name, while someone is looking.
168
+ */
169
+ export declare function patternProblem(pattern: string): string | undefined;
170
+ /**
171
+ * A class whose methods manage the channels `pattern` names (see the top of
172
+ * this file). It is a provider a module lists (its constructor injected as
173
+ * any provider's), built once: what @OnFirst starts and @OnEmpty stops lives
174
+ * in it. Its events are its class's `static readonly events`.
175
+ */
176
+ export declare function Gateway(pattern: string, options?: GatewayOptions): ClassDecorator;
177
+ /** Who may join: answers a GatewayGrant (GatewayAuthorizeContext) — for a gateway with no access of realtime's. */
178
+ export declare const Authorize: () => MethodDecorator;
179
+ /**
180
+ * The channel is occupied and held by no one: start what it needs. Told once
181
+ * across realtime's replicas when its first member comes — before that
182
+ * member is answered — and again only after it emptied.
183
+ */
184
+ export declare const OnFirst: () => MethodDecorator;
185
+ /** A member joined (after it was answered): what it emits goes to that member alone. */
186
+ export declare const OnConnect: () => MethodDecorator;
187
+ /** A member left. */
188
+ export declare const OnDisconnect: () => MethodDecorator;
189
+ /** Nobody has been on the channel for its graceMs: stop what @OnFirst started. Its signal is aborted already. */
190
+ export declare const OnEmpty: () => MethodDecorator;
191
+ /**
192
+ * A message a member sends, `name`, its payload checked against `schema`
193
+ * before the method runs (GatewayMessageContext): a payload that does not
194
+ * fit never reaches it, and the member hears why. What the method returns
195
+ * is the member's ack — `reply` its schema in every generated client.
196
+ */
197
+ export declare function Subscribe(name: string, schema: StandardSchemaV1, options?: {
198
+ reply?: StandardSchemaV1;
199
+ }): MethodDecorator;
200
+ /** The gateway a class declares, or undefined: a gateway is one by its decorator, never its name. */
201
+ export declare function gatewayOf(cls: Function): GatewayDeclaration | undefined;
202
+ /** A gateway's channel's parameters, or undefined when the channel is not one of its. */
203
+ export declare function matchChannel(pattern: string, channel: string): Record<string, string> | undefined;
204
+ /** The channel of a gateway's parameters: each named by its pattern, a whole segment — or why not. */
205
+ export declare function channelOf(pattern: string, params: Readonly<Record<string, unknown>>): string | {
206
+ problem: string;
207
+ };
208
+ /** Whose memberships of a gateway's channel are decided again, and whether the channel is over. */
209
+ export interface RevalidateOptions {
210
+ /** Only this user's membership: the member taken out. */
211
+ user?: string;
212
+ /** The channel is over: every member dropped without being asked, its log forgotten — one channel's, every value given. */
213
+ end?: boolean;
214
+ }
215
+ /**
216
+ * What realtime is asked to decide again for a gateway's channels: the one channel when every capture of its pattern
217
+ * has a value, every channel of the pattern with the values given when some do not — or what is wrong with it, as
218
+ * realtime would refuse it.
219
+ */
220
+ export declare function revalidationOf(pattern: string, params: Readonly<Record<string, unknown>>, options: RevalidateOptions): realtime.Revalidation | {
221
+ problem: string;
222
+ };
223
+ /** One event to a gateway's members, as realtime's broadcast takes it: its channel (topic), and conn_id for one membership alone. */
224
+ type GatewayBroadcast = realtime.BroadcastMessage;
225
+ export interface GatewayDeps {
226
+ /**
227
+ * `work` as the platform's caller names it — in a transaction as `user`,
228
+ * or as the app itself when the moment is no one's (first, empty) —
229
+ * committed unless it throws, or the platform hung up before it ended.
230
+ */
231
+ run<T>(user: SignedUser | undefined, where: string, signal: AbortSignal | undefined, work: () => Promise<T>): Promise<T>;
232
+ /** realtime's POST /realtime/broadcast, as the app (its secret key). */
233
+ broadcast(messages: GatewayBroadcast[]): Promise<unknown>;
234
+ /** realtime's POST /realtime/revalidate, as the app: the memberships it names decided again, on every replica. */
235
+ revalidate(body: realtime.Revalidation): Promise<unknown>;
236
+ /** The platform's infrastructure, not the method, failed: the call is answered 503, and realtime asks again. */
237
+ isOutage(e: unknown): boolean;
238
+ }
239
+ /** What a gateway's call is answered: JSON, nothing (204), or a refusal. */
240
+ export type GatewayAnswer = {
241
+ status: 200;
242
+ body: unknown;
243
+ } | {
244
+ status: 204;
245
+ } | {
246
+ status: number;
247
+ code: string;
248
+ message: string;
249
+ retryAfter?: number;
250
+ };
251
+ export interface Gateways {
252
+ readonly declarations: readonly GatewayDeclaration[];
253
+ /** The platform's call about a gateway — authorize, message or lifecycle — its JSON body. */
254
+ serve(kind: string, body: unknown, signal?: AbortSignal): Promise<GatewayAnswer>;
255
+ /** An event to a gateway's channel from anywhere in the app (Palbase.gateways.to). */
256
+ emit(cls: Function, params: Readonly<Record<string, unknown>>, event: string, payload: unknown): void;
257
+ /** A source piped into a gateway's channel from anywhere in the app (Palbase.gateways.to(…).pipe). */
258
+ pipe(cls: Function, params: Readonly<Record<string, unknown>>, source: PipeSource): Promise<void>;
259
+ /** A gateway's channel — or its channels of the values given — decided again (Palbase.gateways.to(…).revalidate). */
260
+ revalidate(cls: Function, params: Readonly<Record<string, unknown>>, options?: RevalidateOptions): Promise<void>;
261
+ /** Every channel let go: what was held is aborted (the app is stopping). */
262
+ close(): void;
263
+ }
264
+ /** The app's gateways, served: `classes` its providers that are gateways, each with its instance. */
265
+ export declare function createGateways(classes: {
266
+ cls: Function;
267
+ instance: object;
268
+ }[], deps: GatewayDeps): Gateways;
269
+ export {};