@trypattern/sdk 0.1.8 → 0.1.10

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 (88) hide show
  1. package/README.md +37 -8
  2. package/dist/app.d.ts +1 -0
  3. package/dist/app.js +4 -3
  4. package/dist/cli/app.js +120 -7
  5. package/dist/cli/publishing.js +3 -1
  6. package/dist/components/AppFrame.d.ts +5 -1
  7. package/dist/components/AppFrame.js +25 -7
  8. package/dist/components/Appear.d.ts +4 -1
  9. package/dist/components/Appear.js +82 -3
  10. package/dist/components/Card.d.ts +7 -2
  11. package/dist/components/Card.js +59 -3
  12. package/dist/components/Checkbox.d.ts +10 -0
  13. package/dist/components/Checkbox.js +59 -0
  14. package/dist/components/Choice.d.ts +15 -0
  15. package/dist/components/Choice.js +80 -0
  16. package/dist/components/ChoiceGroup.d.ts +11 -0
  17. package/dist/components/ChoiceGroup.js +44 -0
  18. package/dist/components/Icon.d.ts +2 -1
  19. package/dist/components/Icon.js +2 -2
  20. package/dist/components/Image.d.ts +11 -0
  21. package/dist/components/Image.js +76 -0
  22. package/dist/components/Link.js +8 -3
  23. package/dist/components/ListRow.d.ts +2 -1
  24. package/dist/components/ListRow.js +7 -4
  25. package/dist/components/PlainScreen.d.ts +16 -0
  26. package/dist/components/PlainScreen.js +80 -0
  27. package/dist/components/PlainSidebar.d.ts +15 -0
  28. package/dist/components/PlainSidebar.js +179 -0
  29. package/dist/components/Screen.d.ts +3 -1
  30. package/dist/components/Screen.js +5 -1
  31. package/dist/components/Sidebar.js +2 -1
  32. package/dist/components/TextField.d.ts +3 -1
  33. package/dist/components/TextField.js +25 -4
  34. package/dist/components/choiceContext.d.ts +4 -0
  35. package/dist/components/choiceContext.js +7 -0
  36. package/dist/components/frame.d.ts +2 -0
  37. package/dist/components/icons.d.ts +3 -0
  38. package/dist/components/icons.js +4 -1
  39. package/dist/components/interaction.d.ts +1 -1
  40. package/dist/entityStore.d.ts +1 -0
  41. package/dist/entityStore.js +1 -1
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.js +1 -0
  44. package/dist/media.d.ts +36 -0
  45. package/dist/media.js +74 -0
  46. package/dist/patternAccess.d.ts +21 -0
  47. package/dist/patternAccess.js +110 -0
  48. package/dist/publishWire.d.ts +318 -0
  49. package/dist/publishWire.js +128 -1
  50. package/dist/react.d.ts +5 -0
  51. package/dist/react.js +4 -0
  52. package/dist/rules/checkApiRouting.js +1 -1
  53. package/dist/rules/lintPlugin.js +13 -4
  54. package/dist/rules/messages.d.ts +1 -0
  55. package/dist/rules/messages.js +1 -0
  56. package/dist/schema.d.ts +2 -0
  57. package/dist/schema.js +1 -0
  58. package/dist/server/createServer.d.ts +92 -6
  59. package/dist/server/createServer.js +38 -2
  60. package/dist/server/database.d.ts +74 -0
  61. package/dist/server/database.js +93 -0
  62. package/dist/server/env.d.ts +3 -1
  63. package/dist/server/imageHeader.d.ts +10 -0
  64. package/dist/server/imageHeader.js +159 -0
  65. package/dist/server/index.d.ts +6 -1
  66. package/dist/server/index.js +3 -0
  67. package/dist/server/jsonLd.d.ts +45 -0
  68. package/dist/server/jsonLd.js +88 -0
  69. package/dist/server/keyset.d.ts +13 -0
  70. package/dist/server/keyset.js +52 -0
  71. package/dist/server/media.d.ts +16 -0
  72. package/dist/server/media.js +135 -0
  73. package/dist/server/pages.d.ts +72 -0
  74. package/dist/server/pages.js +301 -0
  75. package/dist/server/patternKeys.d.ts +1 -0
  76. package/dist/server/patternKeys.js +3 -0
  77. package/dist/server/patternViewer.d.ts +6 -0
  78. package/dist/server/patternViewer.js +27 -0
  79. package/dist/server/services.js +1 -1
  80. package/dist/server/tables.d.ts +19 -3
  81. package/dist/server/tables.js +38 -14
  82. package/dist/serviceClients.js +3 -5
  83. package/dist/start.js +26 -1
  84. package/dist/table.d.ts +1 -0
  85. package/dist/table.js +8 -1
  86. package/dist/tables.d.ts +2 -1
  87. package/dist/tables.js +10 -1
  88. package/package.json +1 -1
@@ -1,6 +1,8 @@
1
1
  import { z } from 'zod';
2
2
  import { appTitle } from './appTitle.ts';
3
3
  export { appTitle };
4
+ export { ASSERTION_SECONDS, NEXT_PAGE_HEADER, TABLES_READ, assertionKey, signTablesAssertion } from './patternAccess.ts';
5
+ export type { AssertionKey } from './patternAccess.ts';
4
6
  export declare const DASHBOARD_URL = "https://onpattern.app";
5
7
  export declare const APPS_DOMAIN = "onpattern.app";
6
8
  export declare const UPLOAD_LIMIT_BYTES: number;
@@ -18,7 +20,9 @@ export declare const UPLOAD: {
18
20
  readonly compatibilityDate: "compatibilityDate";
19
21
  readonly sdkVersion: "sdkVersion";
20
22
  readonly message: "message";
23
+ readonly runWorkerFirst: "runWorkerFirst";
21
24
  };
25
+ export declare function workerFirstPaths(text: string | undefined): readonly string[];
22
26
  export declare const PUBLISH_MESSAGE_LIMIT = 100;
23
27
  export declare function publishMessage(text: string): string | null;
24
28
  export declare const publishStep: z.ZodEnum<{
@@ -116,6 +120,176 @@ export declare const connection: z.ZodObject<{
116
120
  connectedAt: z.ZodString;
117
121
  }, z.core.$strip>;
118
122
  export type Connection = z.output<typeof connection>;
123
+ export declare const DOMAIN_ENDINGS: readonly ["com", "ca", "co", "org", "net", "app", "dev", "shop"];
124
+ export declare const DOMAIN_PRICE_LIMIT_USD = 30;
125
+ export declare const domainName: z.ZodString;
126
+ export declare const domainChoice: z.ZodObject<{
127
+ domain: z.ZodString;
128
+ priceUsd: z.ZodNullable<z.ZodString>;
129
+ isAvailable: z.ZodBoolean;
130
+ }, z.core.$strip>;
131
+ export type DomainChoice = z.output<typeof domainChoice>;
132
+ export declare const domainSteps: readonly ["register", "address", "sign-in", "reachable"];
133
+ export declare const domainSummary: z.ZodObject<{
134
+ domain: z.ZodString;
135
+ priceUsd: z.ZodString;
136
+ status: z.ZodEnum<{
137
+ failed: "failed";
138
+ live: "live";
139
+ working: "working";
140
+ }>;
141
+ step: z.ZodEnum<{
142
+ address: "address";
143
+ "sign-in": "sign-in";
144
+ register: "register";
145
+ reachable: "reachable";
146
+ }>;
147
+ error: z.ZodNullable<z.ZodString>;
148
+ startedAt: z.ZodString;
149
+ checkedAt: z.ZodNullable<z.ZodString>;
150
+ finishedAt: z.ZodNullable<z.ZodString>;
151
+ }, z.core.$strip>;
152
+ export type DomainSummary = z.output<typeof domainSummary>;
153
+ export declare const mailboxSummary: z.ZodObject<{
154
+ address: z.ZodString;
155
+ destination: z.ZodString;
156
+ status: z.ZodEnum<{
157
+ failed: "failed";
158
+ ready: "ready";
159
+ verifying: "verifying";
160
+ }>;
161
+ error: z.ZodNullable<z.ZodString>;
162
+ }, z.core.$strip>;
163
+ export type MailboxSummary = z.output<typeof mailboxSummary>;
164
+ export declare const mailboxLocalPart: z.ZodString;
165
+ export declare const DATABASE_PAGE_ROWS = 100;
166
+ export declare const databaseTable: z.ZodObject<{
167
+ name: z.ZodString;
168
+ schema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
169
+ access: z.ZodObject<{
170
+ read: z.ZodEnum<{
171
+ anyone: "anyone";
172
+ signedIn: "signedIn";
173
+ owner: "owner";
174
+ admin: "admin";
175
+ server: "server";
176
+ }>;
177
+ create: z.ZodEnum<{
178
+ anyone: "anyone";
179
+ signedIn: "signedIn";
180
+ owner: "owner";
181
+ admin: "admin";
182
+ server: "server";
183
+ }>;
184
+ update: z.ZodEnum<{
185
+ anyone: "anyone";
186
+ signedIn: "signedIn";
187
+ owner: "owner";
188
+ admin: "admin";
189
+ server: "server";
190
+ }>;
191
+ delete: z.ZodEnum<{
192
+ anyone: "anyone";
193
+ signedIn: "signedIn";
194
+ owner: "owner";
195
+ admin: "admin";
196
+ server: "server";
197
+ }>;
198
+ }, z.core.$strip>;
199
+ public: z.ZodArray<z.ZodString>;
200
+ rowCount: z.ZodNumber;
201
+ isBuiltIn: z.ZodBoolean;
202
+ }, z.core.$strip>;
203
+ export type DatabaseTable = z.output<typeof databaseTable>;
204
+ export declare const appSchema: z.ZodObject<{
205
+ tables: z.ZodArray<z.ZodObject<{
206
+ name: z.ZodString;
207
+ schema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
208
+ access: z.ZodObject<{
209
+ read: z.ZodEnum<{
210
+ anyone: "anyone";
211
+ signedIn: "signedIn";
212
+ owner: "owner";
213
+ admin: "admin";
214
+ server: "server";
215
+ }>;
216
+ create: z.ZodEnum<{
217
+ anyone: "anyone";
218
+ signedIn: "signedIn";
219
+ owner: "owner";
220
+ admin: "admin";
221
+ server: "server";
222
+ }>;
223
+ update: z.ZodEnum<{
224
+ anyone: "anyone";
225
+ signedIn: "signedIn";
226
+ owner: "owner";
227
+ admin: "admin";
228
+ server: "server";
229
+ }>;
230
+ delete: z.ZodEnum<{
231
+ anyone: "anyone";
232
+ signedIn: "signedIn";
233
+ owner: "owner";
234
+ admin: "admin";
235
+ server: "server";
236
+ }>;
237
+ }, z.core.$strip>;
238
+ public: z.ZodArray<z.ZodString>;
239
+ rowCount: z.ZodNumber;
240
+ isBuiltIn: z.ZodBoolean;
241
+ }, z.core.$strip>>;
242
+ }, z.core.$strip>;
243
+ export declare const databaseSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
244
+ status: z.ZodLiteral<"ready">;
245
+ tables: z.ZodArray<z.ZodObject<{
246
+ name: z.ZodString;
247
+ schema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
248
+ access: z.ZodObject<{
249
+ read: z.ZodEnum<{
250
+ anyone: "anyone";
251
+ signedIn: "signedIn";
252
+ owner: "owner";
253
+ admin: "admin";
254
+ server: "server";
255
+ }>;
256
+ create: z.ZodEnum<{
257
+ anyone: "anyone";
258
+ signedIn: "signedIn";
259
+ owner: "owner";
260
+ admin: "admin";
261
+ server: "server";
262
+ }>;
263
+ update: z.ZodEnum<{
264
+ anyone: "anyone";
265
+ signedIn: "signedIn";
266
+ owner: "owner";
267
+ admin: "admin";
268
+ server: "server";
269
+ }>;
270
+ delete: z.ZodEnum<{
271
+ anyone: "anyone";
272
+ signedIn: "signedIn";
273
+ owner: "owner";
274
+ admin: "admin";
275
+ server: "server";
276
+ }>;
277
+ }, z.core.$strip>;
278
+ public: z.ZodArray<z.ZodString>;
279
+ rowCount: z.ZodNumber;
280
+ isBuiltIn: z.ZodBoolean;
281
+ }, z.core.$strip>>;
282
+ }, z.core.$strip>, z.ZodObject<{
283
+ status: z.ZodLiteral<"outdated">;
284
+ }, z.core.$strip>], "status">;
285
+ export type DatabaseSchema = z.output<typeof databaseSchema>;
286
+ export declare const databaseRow: z.ZodRecord<z.ZodString, z.ZodUnknown>;
287
+ export type DatabaseRow = z.output<typeof databaseRow>;
288
+ export declare const databaseRows: z.ZodObject<{
289
+ rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
290
+ next: z.ZodNullable<z.ZodString>;
291
+ }, z.core.$strip>;
292
+ export type DatabaseRows = z.output<typeof databaseRows>;
119
293
  export declare const publishServices: {
120
294
  readonly apps: {
121
295
  readonly list: NoInfer<import("./services.ts").ServiceCall<"/apps", {}, z.ZodArray<z.ZodObject<{
@@ -295,6 +469,150 @@ export declare const publishServices: {
295
469
  ok: z.ZodBoolean;
296
470
  }, z.core.$strip>>>;
297
471
  };
472
+ readonly domains: {
473
+ readonly search: NoInfer<import("./services.ts").ServiceCall<"/domains", {
474
+ query: z.ZodString;
475
+ }, z.ZodArray<z.ZodObject<{
476
+ domain: z.ZodString;
477
+ priceUsd: z.ZodNullable<z.ZodString>;
478
+ isAvailable: z.ZodBoolean;
479
+ }, z.core.$strip>>>>;
480
+ readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/domain", {}, z.ZodNullable<z.ZodObject<{
481
+ domain: z.ZodString;
482
+ priceUsd: z.ZodString;
483
+ status: z.ZodEnum<{
484
+ failed: "failed";
485
+ live: "live";
486
+ working: "working";
487
+ }>;
488
+ step: z.ZodEnum<{
489
+ address: "address";
490
+ "sign-in": "sign-in";
491
+ register: "register";
492
+ reachable: "reachable";
493
+ }>;
494
+ error: z.ZodNullable<z.ZodString>;
495
+ startedAt: z.ZodString;
496
+ checkedAt: z.ZodNullable<z.ZodString>;
497
+ finishedAt: z.ZodNullable<z.ZodString>;
498
+ }, z.core.$strip>>>>;
499
+ readonly buy: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/domain", {
500
+ domain: z.ZodString;
501
+ priceUsd: z.ZodString;
502
+ }, z.ZodObject<{
503
+ domain: z.ZodString;
504
+ priceUsd: z.ZodString;
505
+ status: z.ZodEnum<{
506
+ failed: "failed";
507
+ live: "live";
508
+ working: "working";
509
+ }>;
510
+ step: z.ZodEnum<{
511
+ address: "address";
512
+ "sign-in": "sign-in";
513
+ register: "register";
514
+ reachable: "reachable";
515
+ }>;
516
+ error: z.ZodNullable<z.ZodString>;
517
+ startedAt: z.ZodString;
518
+ checkedAt: z.ZodNullable<z.ZodString>;
519
+ finishedAt: z.ZodNullable<z.ZodString>;
520
+ }, z.core.$strip>>>;
521
+ readonly retry: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/domain/retry", {}, z.ZodObject<{
522
+ domain: z.ZodString;
523
+ priceUsd: z.ZodString;
524
+ status: z.ZodEnum<{
525
+ failed: "failed";
526
+ live: "live";
527
+ working: "working";
528
+ }>;
529
+ step: z.ZodEnum<{
530
+ address: "address";
531
+ "sign-in": "sign-in";
532
+ register: "register";
533
+ reachable: "reachable";
534
+ }>;
535
+ error: z.ZodNullable<z.ZodString>;
536
+ startedAt: z.ZodString;
537
+ checkedAt: z.ZodNullable<z.ZodString>;
538
+ finishedAt: z.ZodNullable<z.ZodString>;
539
+ }, z.core.$strip>>>;
540
+ };
541
+ readonly mailboxes: {
542
+ readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/mailbox", {}, z.ZodNullable<z.ZodObject<{
543
+ address: z.ZodString;
544
+ destination: z.ZodString;
545
+ status: z.ZodEnum<{
546
+ failed: "failed";
547
+ ready: "ready";
548
+ verifying: "verifying";
549
+ }>;
550
+ error: z.ZodNullable<z.ZodString>;
551
+ }, z.core.$strip>>>>;
552
+ readonly create: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/mailbox", {
553
+ localPart: z.ZodString;
554
+ destination: z.ZodEmail;
555
+ }, z.ZodObject<{
556
+ address: z.ZodString;
557
+ destination: z.ZodString;
558
+ status: z.ZodEnum<{
559
+ failed: "failed";
560
+ ready: "ready";
561
+ verifying: "verifying";
562
+ }>;
563
+ error: z.ZodNullable<z.ZodString>;
564
+ }, z.core.$strip>>>;
565
+ };
566
+ readonly database: {
567
+ readonly schema: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/database", {}, z.ZodDiscriminatedUnion<[z.ZodObject<{
568
+ status: z.ZodLiteral<"ready">;
569
+ tables: z.ZodArray<z.ZodObject<{
570
+ name: z.ZodString;
571
+ schema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
572
+ access: z.ZodObject<{
573
+ read: z.ZodEnum<{
574
+ anyone: "anyone";
575
+ signedIn: "signedIn";
576
+ owner: "owner";
577
+ admin: "admin";
578
+ server: "server";
579
+ }>;
580
+ create: z.ZodEnum<{
581
+ anyone: "anyone";
582
+ signedIn: "signedIn";
583
+ owner: "owner";
584
+ admin: "admin";
585
+ server: "server";
586
+ }>;
587
+ update: z.ZodEnum<{
588
+ anyone: "anyone";
589
+ signedIn: "signedIn";
590
+ owner: "owner";
591
+ admin: "admin";
592
+ server: "server";
593
+ }>;
594
+ delete: z.ZodEnum<{
595
+ anyone: "anyone";
596
+ signedIn: "signedIn";
597
+ owner: "owner";
598
+ admin: "admin";
599
+ server: "server";
600
+ }>;
601
+ }, z.core.$strip>;
602
+ public: z.ZodArray<z.ZodString>;
603
+ rowCount: z.ZodNumber;
604
+ isBuiltIn: z.ZodBoolean;
605
+ }, z.core.$strip>>;
606
+ }, z.core.$strip>, z.ZodObject<{
607
+ status: z.ZodLiteral<"outdated">;
608
+ }, z.core.$strip>], "status">>>;
609
+ readonly rows: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/database/:table", {
610
+ after: z.ZodOptional<z.ZodString>;
611
+ }, z.ZodObject<{
612
+ rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
613
+ next: z.ZodNullable<z.ZodString>;
614
+ }, z.core.$strip>>>;
615
+ };
298
616
  readonly logins: {
299
617
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/cli/logins/:approval", {}, z.ZodObject<{
300
618
  code: z.ZodString;
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { appTitle } from "./appTitle.js";
3
3
  export { appTitle };
4
+ export { ASSERTION_SECONDS, NEXT_PAGE_HEADER, TABLES_READ, assertionKey, signTablesAssertion } from "./patternAccess.js";
4
5
  import { call, defineServices } from "./services.js";
5
6
  export const DASHBOARD_URL = 'https://onpattern.app';
6
7
  export const APPS_DOMAIN = 'onpattern.app';
@@ -90,8 +91,28 @@ export const UPLOAD = {
90
91
  source: 'source/',
91
92
  compatibilityDate: 'compatibilityDate',
92
93
  sdkVersion: 'sdkVersion',
93
- message: 'message'
94
+ message: 'message',
95
+ runWorkerFirst: 'runWorkerFirst'
94
96
  };
97
+ const WORKER_FIRST_PATH = /^!?\/[\w.~\-/*]{0,100}$/;
98
+ const WORKER_FIRST_LIMIT = 32;
99
+ const ALWAYS_WORKER_FIRST = ['/api/*', '/media/*'];
100
+ export function workerFirstPaths(text) {
101
+ let declared;
102
+ try {
103
+ declared = JSON.parse(text ?? '[]');
104
+ }
105
+ catch {
106
+ declared = [];
107
+ }
108
+ const listed = declared === true ? ['/*'] : declared;
109
+ const extra = (Array.isArray(listed) ? listed : [])
110
+ .slice(0, WORKER_FIRST_LIMIT)
111
+ .filter((path) => typeof path === 'string' &&
112
+ WORKER_FIRST_PATH.test(path) &&
113
+ !ALWAYS_WORKER_FIRST.some((kept) => path.startsWith(`!${kept.slice(0, -1)}`)));
114
+ return [...new Set([...ALWAYS_WORKER_FIRST, ...extra])];
115
+ }
95
116
  export const PUBLISH_MESSAGE_LIMIT = 100;
96
117
  export function publishMessage(text) {
97
118
  const firstLine = (text.split('\n')[0] ?? '').replaceAll(/\s+/g, ' ').trim();
@@ -139,6 +160,79 @@ export const connection = z.object({
139
160
  label: z.string(),
140
161
  connectedAt: z.string()
141
162
  });
163
+ export const DOMAIN_ENDINGS = [
164
+ 'com',
165
+ 'ca',
166
+ 'co',
167
+ 'org',
168
+ 'net',
169
+ 'app',
170
+ 'dev',
171
+ 'shop'
172
+ ];
173
+ export const DOMAIN_PRICE_LIMIT_USD = 30;
174
+ export const domainName = z
175
+ .string()
176
+ .trim()
177
+ .toLowerCase()
178
+ .regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/);
179
+ export const domainChoice = z.object({
180
+ domain: z.string(),
181
+ priceUsd: z.string().nullable(),
182
+ isAvailable: z.boolean()
183
+ });
184
+ export const domainSteps = [
185
+ 'register',
186
+ 'address',
187
+ 'sign-in',
188
+ 'reachable'
189
+ ];
190
+ export const domainSummary = z.object({
191
+ domain: z.string(),
192
+ priceUsd: z.string(),
193
+ status: z.enum(['working', 'live', 'failed']),
194
+ step: z.enum(domainSteps),
195
+ error: z.string().nullable(),
196
+ startedAt: z.string(),
197
+ checkedAt: z.string().nullable(),
198
+ finishedAt: z.string().nullable()
199
+ });
200
+ export const mailboxSummary = z.object({
201
+ address: z.string(),
202
+ destination: z.string(),
203
+ status: z.enum(['verifying', 'ready', 'failed']),
204
+ error: z.string().nullable()
205
+ });
206
+ export const mailboxLocalPart = z
207
+ .string()
208
+ .trim()
209
+ .toLowerCase()
210
+ .regex(/^[a-z0-9]([a-z0-9._-]{0,62}[a-z0-9])?$/);
211
+ export const DATABASE_PAGE_ROWS = 100;
212
+ const accessLevel = z.enum(['anyone', 'signedIn', 'owner', 'admin', 'server']);
213
+ export const databaseTable = z.object({
214
+ name: z.string(),
215
+ schema: z.record(z.string(), z.unknown()),
216
+ access: z.object({
217
+ read: accessLevel,
218
+ create: accessLevel,
219
+ update: accessLevel,
220
+ delete: accessLevel
221
+ }),
222
+ public: z.array(z.string()),
223
+ rowCount: z.number(),
224
+ isBuiltIn: z.boolean()
225
+ });
226
+ export const appSchema = z.object({ tables: z.array(databaseTable) });
227
+ export const databaseSchema = z.discriminatedUnion('status', [
228
+ z.object({ status: z.literal('ready'), tables: z.array(databaseTable) }),
229
+ z.object({ status: z.literal('outdated') })
230
+ ]);
231
+ export const databaseRow = z.record(z.string(), z.unknown());
232
+ export const databaseRows = z.object({
233
+ rows: z.array(databaseRow),
234
+ next: z.string().nullable()
235
+ });
142
236
  export const publishServices = defineServices({
143
237
  apps: {
144
238
  list: call({ get: '/apps', reply: z.array(appSummary) }),
@@ -182,6 +276,39 @@ export const publishServices = defineServices({
182
276
  reply: z.object({ ok: z.boolean() })
183
277
  })
184
278
  },
279
+ domains: {
280
+ search: call({
281
+ get: '/domains',
282
+ input: { query: z.string() },
283
+ reply: z.array(domainChoice)
284
+ }),
285
+ read: call({ get: '/apps/:name/domain', reply: domainSummary.nullable() }),
286
+ buy: call({
287
+ post: '/apps/:name/domain',
288
+ input: { domain: domainName, priceUsd: z.string() },
289
+ reply: domainSummary
290
+ }),
291
+ retry: call({ post: '/apps/:name/domain/retry', reply: domainSummary })
292
+ },
293
+ mailboxes: {
294
+ read: call({
295
+ get: '/apps/:name/mailbox',
296
+ reply: mailboxSummary.nullable()
297
+ }),
298
+ create: call({
299
+ post: '/apps/:name/mailbox',
300
+ input: { localPart: mailboxLocalPart, destination: z.email() },
301
+ reply: mailboxSummary
302
+ })
303
+ },
304
+ database: {
305
+ schema: call({ get: '/apps/:name/database', reply: databaseSchema }),
306
+ rows: call({
307
+ get: '/apps/:name/database/:table',
308
+ input: { after: z.string().optional() },
309
+ reply: databaseRows
310
+ })
311
+ },
185
312
  logins: {
186
313
  read: call({ get: '/cli/logins/:approval', reply: loginToApprove }),
187
314
  approve: call({
package/dist/react.d.ts CHANGED
@@ -6,6 +6,9 @@ export { Avatar } from './components/Avatar.tsx';
6
6
  export { Badge } from './components/Badge.tsx';
7
7
  export { Button } from './components/Button.tsx';
8
8
  export { Card } from './components/Card.tsx';
9
+ export { Checkbox } from './components/Checkbox.tsx';
10
+ export { Choice } from './components/Choice.tsx';
11
+ export { ChoiceGroup } from './components/ChoiceGroup.tsx';
9
12
  export { CommandMenu } from './components/CommandMenu.tsx';
10
13
  export { CommandPalette } from './components/CommandPalette.tsx';
11
14
  export type { CommandMenuItem } from './components/CommandMenu.tsx';
@@ -18,6 +21,7 @@ export { EmptyState } from './components/EmptyState.tsx';
18
21
  export { Form } from './components/Form.tsx';
19
22
  export { Icon } from './components/Icon.tsx';
20
23
  export { IconButton } from './components/IconButton.tsx';
24
+ export { Image } from './components/Image.tsx';
21
25
  export type { IconName } from './components/icons.ts';
22
26
  export { Kbd } from './components/Kbd.tsx';
23
27
  export { Link } from './components/Link.tsx';
@@ -48,3 +52,4 @@ export { Text } from './components/Text.tsx';
48
52
  export { TextField } from './components/TextField.tsx';
49
53
  export { Toaster } from './components/Toaster.tsx';
50
54
  export { Tooltip } from './components/Tooltip.tsx';
55
+ export type { MediaImage } from './media.ts';
package/dist/react.js CHANGED
@@ -6,6 +6,9 @@ export { Avatar } from "./components/Avatar.js";
6
6
  export { Badge } from "./components/Badge.js";
7
7
  export { Button } from "./components/Button.js";
8
8
  export { Card } from "./components/Card.js";
9
+ export { Checkbox } from "./components/Checkbox.js";
10
+ export { Choice } from "./components/Choice.js";
11
+ export { ChoiceGroup } from "./components/ChoiceGroup.js";
9
12
  export { CommandMenu } from "./components/CommandMenu.js";
10
13
  export { CommandPalette } from "./components/CommandPalette.js";
11
14
  export { Content } from "./components/Content.js";
@@ -17,6 +20,7 @@ export { EmptyState } from "./components/EmptyState.js";
17
20
  export { Form } from "./components/Form.js";
18
21
  export { Icon } from "./components/Icon.js";
19
22
  export { IconButton } from "./components/IconButton.js";
23
+ export { Image } from "./components/Image.js";
20
24
  export { Kbd } from "./components/Kbd.js";
21
25
  export { Link } from "./components/Link.js";
22
26
  export { ListRow } from "./components/ListRow.js";
@@ -2,7 +2,7 @@ import { request } from 'node:http';
2
2
  import { createServer as createNetServer } from 'node:net';
3
3
  import { createServer, preview } from 'vite';
4
4
  const API_ROUTES_REACH_THE_WORKER = 'Browser navigations to /api/* (pasted links, OAuth callbacks) must reach the Hono worker, not the single-page app. Cloudflare serves index.html to navigations without running the worker unless wrangler.jsonc keeps "run_worker_first": ["/api/*"] under "assets". Without it, an OAuth callback lands on the app and loops.';
5
- const APP_ROUTES_SERVE_THE_APP = 'Browser navigations outside /api/ must serve the app (index.html) so deep links like /issues open the screen. Keep "not_found_handling": "single-page-application" in wrangler.jsonc and keep run_worker_first limited to /api/*.';
5
+ const APP_ROUTES_SERVE_THE_APP = 'Browser navigations outside /api/ must serve the app (index.html) so deep links like /issues open the screen. Keep "not_found_handling": "single-page-application" in wrangler.jsonc and keep run_worker_first to /api/* (plus /media/*, and the public page paths, /sitemap.xml and /robots.txt for an app with pages).';
6
6
  const DEV_SIGN_IN_ONLY_IN_DEV = 'POST /api/auth/dev-sign-in signs anyone in without Google, so it must exist in the dev server (app sign-in, flows and screenshots use it) and must not exist in the production build. createServer in @trypattern/sdk mounts it only under import.meta.env.DEV.';
7
7
  const probes = [
8
8
  {
@@ -1,4 +1,4 @@
1
- import { ACTIONS_WHERE_MESSAGE, COMPONENT_FIRST_MESSAGE, COMPONENT_FILE_NAME_MESSAGE, DARK_MODE_IN_TOKENS_MESSAGE, DEV_ONLY_SIGN_IN_MESSAGE, EXPLICIT_ACCESS_MESSAGE, USE_BREAKPOINTS_MESSAGE, HOVER_USES_HIGHLIGHT_MESSAGE, NO_CLASSNAME_STRINGS_MESSAGE, NO_COMMENTS_MESSAGE, NO_INTERACTIVE_TRANSITION_MESSAGE, NO_POINTER_CURSOR_MESSAGE, NO_ROUTE_DATA_APIS_MESSAGE, ROUTES_UNDER_APP_LAYOUT_MESSAGE, SCROLL_VIEW_ONLY_MESSAGE, SELECTABLE_ONLY_MESSAGE, STATE_VIA_HOOKS_MESSAGE, MODEL_BY_NAME_MESSAGE, MODEL_MEMBERS_MESSAGE, PURE_ACTIONS_MESSAGE, PURE_GETTERS_MESSAGE, USE_MODEL_WHERE_MESSAGE, CLASSES_NOT_FACTORIES_MESSAGE, STORES_IN_APP_MESSAGE, TYPED_STYLE_PROP_MESSAGE, USE_COLOR_TOKEN_MESSAGE, USE_FONT_TOKEN_MESSAGE, USE_LINK_MESSAGE, USE_PRIMITIVES_MESSAGE, NO_WIDGET_ROLES_MESSAGE, NO_CLICK_ON_STATIC_MESSAGE, PRIMITIVE_NAMES_MESSAGE, USE_SPACE_TOKENS_MESSAGE, USE_SIZE_TOKENS_MESSAGE, TEXT_VIA_TEXT_MESSAGE, TEXT_ELEMENTS_MESSAGE, LOOSE_TEXT_MESSAGE, COMPOSE_IN_SCREENS_MESSAGE, NO_HOVER_IN_APP_MESSAGE } from "./messages.js";
1
+ import { ACTIONS_WHERE_MESSAGE, COMPONENT_FIRST_MESSAGE, COMPONENT_FILE_NAME_MESSAGE, DARK_MODE_IN_TOKENS_MESSAGE, DEV_ONLY_SIGN_IN_MESSAGE, EXPLICIT_ACCESS_MESSAGE, USE_BREAKPOINTS_MESSAGE, HOVER_USES_HIGHLIGHT_MESSAGE, NO_CLASSNAME_STRINGS_MESSAGE, NO_COMMENTS_MESSAGE, NO_INTERACTIVE_TRANSITION_MESSAGE, NO_POINTER_CURSOR_MESSAGE, NO_ROUTE_DATA_APIS_MESSAGE, ROUTES_UNDER_APP_LAYOUT_MESSAGE, SCROLL_VIEW_ONLY_MESSAGE, SELECTABLE_ONLY_MESSAGE, STATE_VIA_HOOKS_MESSAGE, MODEL_BY_NAME_MESSAGE, MODEL_MEMBERS_MESSAGE, PURE_ACTIONS_MESSAGE, PURE_GETTERS_MESSAGE, USE_MODEL_WHERE_MESSAGE, CLASSES_NOT_FACTORIES_MESSAGE, STORES_IN_APP_MESSAGE, TYPED_STYLE_PROP_MESSAGE, USE_COLOR_TOKEN_MESSAGE, USE_FONT_TOKEN_MESSAGE, USE_LINK_MESSAGE, USE_PRIMITIVES_MESSAGE, USE_IMAGE_MESSAGE, NO_WIDGET_ROLES_MESSAGE, NO_CLICK_ON_STATIC_MESSAGE, PRIMITIVE_NAMES_MESSAGE, USE_SPACE_TOKENS_MESSAGE, USE_SIZE_TOKENS_MESSAGE, TEXT_VIA_TEXT_MESSAGE, TEXT_ELEMENTS_MESSAGE, LOOSE_TEXT_MESSAGE, COMPOSE_IN_SCREENS_MESSAGE, NO_HOVER_IN_APP_MESSAGE } from "./messages.js";
2
2
  const TOKENS_FILE = 'src/theme/tokens.stylex.ts';
3
3
  const APP_THEME_FILE = 'src/theme/theme.ts';
4
4
  const SCROLL_AREA_FILE = 'src/components/ScrollArea.tsx';
@@ -10,7 +10,8 @@ const PRIMITIVE_ELEMENTS = new Set([
10
10
  'select',
11
11
  'dialog',
12
12
  'progress',
13
- 'meter'
13
+ 'meter',
14
+ 'img'
14
15
  ]);
15
16
  const WIDGET_ROLES = new Set([
16
17
  'button',
@@ -50,6 +51,9 @@ const PRIMITIVES = new Set([
50
51
  'Badge',
51
52
  'Button',
52
53
  'Card',
54
+ 'Checkbox',
55
+ 'Choice',
56
+ 'ChoiceGroup',
53
57
  'CommandMenu',
54
58
  'CommandPalette',
55
59
  'Content',
@@ -61,6 +65,7 @@ const PRIMITIVES = new Set([
61
65
  'Form',
62
66
  'Icon',
63
67
  'IconButton',
68
+ 'Image',
64
69
  'Kbd',
65
70
  'Link',
66
71
  'ListRow',
@@ -939,8 +944,12 @@ const usePrimitives = rule((context) => ({
939
944
  JSXOpeningElement(node) {
940
945
  if (!SRC_FILE.test(context.filename))
941
946
  return;
942
- if (PRIMITIVE_ELEMENTS.has(elementName(node) ?? '')) {
943
- context.report({ loc: node.loc, message: USE_PRIMITIVES_MESSAGE });
947
+ const name = elementName(node) ?? '';
948
+ if (PRIMITIVE_ELEMENTS.has(name)) {
949
+ context.report({
950
+ loc: node.loc,
951
+ message: name === 'img' ? USE_IMAGE_MESSAGE : USE_PRIMITIVES_MESSAGE
952
+ });
944
953
  }
945
954
  }
946
955
  }));
@@ -34,6 +34,7 @@ export declare const PURE_GETTERS_MESSAGE = "A getter works an answer out from t
34
34
  export declare const ACTIONS_WHERE_MESSAGE = "Menu entries and shortcuts are actions, and actions live only in src/actions/: one file per thing they act on, `export const issueActions = defineActions(({ page, models }) => [action({ id: 'issues.next', label: 'Next issue', keys: ['j'], send: [{ type: 'issueDetail.openNext' }] })])`, listed in src/actions/index.ts. If a person can see it in a menu or press a key for it, it is an action; what it does is a command on a model, and an action only sends commands. Models never build actions.";
35
35
  export declare const PURE_ACTIONS_MESSAGE = "An actions file works its entries out from `page` and `models` (each model's state and answers) and nothing else, because Pattern reruns it only when something it read changes, and flows compare the result. It never waits, never uses `this`, never reads the clock or randomness, and imports only types from src/models/, src/stores/ and src/components/. If an entry needs something a model does not answer yet, add a getter to that model.";
36
36
  export declare const USE_PRIMITIVES_MESSAGE = "Write <Button>, <IconButton>, <TextField>, <Select>, <Switch> or <Dialog> from @trypattern/sdk/react instead of this element. The primitives carry the focus, keyboard, disabled, pending and error behaviour and the design system's look, so a screen only says what the control does. If no primitive fits, the gap is in the SDK; do not build around it here.";
37
+ export declare const USE_IMAGE_MESSAGE = "Write <Image image={photo} sizes=\"\u2026\" /> from @trypattern/sdk/react instead of <img>. Photos live in the media table (app media add photo.jpg --alt \"\u2026\"); Image reserves their size, picks the right width for the screen, shows a blurred placeholder while loading and a neutral block when one is missing. Never hard-code an image URL.";
37
38
  export declare const NO_WIDGET_ROLES_MESSAGE = "This role rebuilds a primitive by hand. Use the component from @trypattern/sdk/react with that behaviour (Button, Switch, Menu, Tooltip, Dialog, Divider, CommandMenu): it already has the role, the keyboard handling and the focus management, and it will keep working when the SDK is upgraded.";
38
39
  export declare const NO_CLICK_ON_STATIC_MESSAGE = "A click on a <div> or <span> cannot be reached from the keyboard or by a screen reader. Make it a <Button variant=\"transparent\"> or an <IconButton> from @trypattern/sdk/react, or a <Link> if it goes somewhere.";
39
40
  export declare const PRIMITIVE_NAMES_MESSAGE = "{name} is a primitive in @trypattern/sdk/react; a second one with the same name would drift from it. Import the SDK's, and if yours adds something (a fixed icon, a confirm step) name it for what it does, like DeleteIssue{name}, and render the SDK {name} inside.";
@@ -34,6 +34,7 @@ export const PURE_GETTERS_MESSAGE = 'A getter works an answer out from this.stat
34
34
  export const ACTIONS_WHERE_MESSAGE = "Menu entries and shortcuts are actions, and actions live only in src/actions/: one file per thing they act on, `export const issueActions = defineActions(({ page, models }) => [action({ id: 'issues.next', label: 'Next issue', keys: ['j'], send: [{ type: 'issueDetail.openNext' }] })])`, listed in src/actions/index.ts. If a person can see it in a menu or press a key for it, it is an action; what it does is a command on a model, and an action only sends commands. Models never build actions.";
35
35
  export const PURE_ACTIONS_MESSAGE = "An actions file works its entries out from `page` and `models` (each model's state and answers) and nothing else, because Pattern reruns it only when something it read changes, and flows compare the result. It never waits, never uses `this`, never reads the clock or randomness, and imports only types from src/models/, src/stores/ and src/components/. If an entry needs something a model does not answer yet, add a getter to that model.";
36
36
  export const USE_PRIMITIVES_MESSAGE = "Write <Button>, <IconButton>, <TextField>, <Select>, <Switch> or <Dialog> from @trypattern/sdk/react instead of this element. The primitives carry the focus, keyboard, disabled, pending and error behaviour and the design system's look, so a screen only says what the control does. If no primitive fits, the gap is in the SDK; do not build around it here.";
37
+ export const USE_IMAGE_MESSAGE = 'Write <Image image={photo} sizes="…" /> from @trypattern/sdk/react instead of <img>. Photos live in the media table (app media add photo.jpg --alt "…"); Image reserves their size, picks the right width for the screen, shows a blurred placeholder while loading and a neutral block when one is missing. Never hard-code an image URL.';
37
38
  export const NO_WIDGET_ROLES_MESSAGE = 'This role rebuilds a primitive by hand. Use the component from @trypattern/sdk/react with that behaviour (Button, Switch, Menu, Tooltip, Dialog, Divider, CommandMenu): it already has the role, the keyboard handling and the focus management, and it will keep working when the SDK is upgraded.';
38
39
  export const NO_CLICK_ON_STATIC_MESSAGE = 'A click on a <div> or <span> cannot be reached from the keyboard or by a screen reader. Make it a <Button variant="transparent"> or an <IconButton> from @trypattern/sdk/react, or a <Link> if it goes somewhere.';
39
40
  export const PRIMITIVE_NAMES_MESSAGE = "{name} is a primitive in @trypattern/sdk/react; a second one with the same name would drift from it. Import the SDK's, and if yours adds something (a fixed icon, a confirm step) name it for what it does, like DeleteIssue{name}, and render the SDK {name} inside.";
package/dist/schema.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export { call, defineServices } from './services.ts';
2
2
  export type { CallInput, CallReply, ServiceCall, ServiceMemory, Services } from './services.ts';
3
+ export { media } from './media.ts';
4
+ export type { Media, MediaImage } from './media.ts';
3
5
  export { defineTables } from './table.ts';
4
6
  export type { Access, Columns, Fields, Level, PublicRow, Row, Seed, Table, TableDefinition, TableOf, Tables, Value, Writes } from './table.ts';
package/dist/schema.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { call, defineServices } from "./services.js";
2
+ export { media } from "./media.js";
2
3
  export { defineTables } from "./table.js";