@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
@@ -0,0 +1,3 @@
1
+ export const PATTERN_KEYS = {
2
+ 'pattern-2026-10': 'R0VOZWJtSyBR2jwbN1U3nci1WImH9ZTy27KIw4igaXs'
3
+ };
@@ -0,0 +1,6 @@
1
+ import type { Context, Next } from 'hono';
2
+ import type { ServerEnv } from './env.ts';
3
+ export declare function bearerOf(c: Context<ServerEnv>): string | undefined;
4
+ export declare function attachPatternViewer(c: Context<ServerEnv>, next: Next, token: string): Promise<void | (Response & import("hono").TypedResponse<{
5
+ error: string;
6
+ }, 401, "json">)>;
@@ -0,0 +1,27 @@
1
+ import { verifiedTablesAssertion } from "../patternAccess.js";
2
+ import { PATTERN_KEYS } from "./patternKeys.js";
3
+ const BEARER = /^bearer\s+/i;
4
+ const SCHEMA_PATH = '/api/schema';
5
+ const TABLES_PATH = '/api/tables/';
6
+ export function bearerOf(c) {
7
+ const header = c.req.header('authorization') ?? '';
8
+ return BEARER.test(header) ? header.replace(BEARER, '').trim() : undefined;
9
+ }
10
+ function isReadByPattern(c) {
11
+ const { path } = c.req;
12
+ return (c.req.method === 'GET' &&
13
+ (path === SCHEMA_PATH || path.startsWith(TABLES_PATH)));
14
+ }
15
+ export async function attachPatternViewer(c, next, token) {
16
+ const email = isReadByPattern(c)
17
+ ? await verifiedTablesAssertion(token, {
18
+ hostname: new URL(c.req.url).hostname,
19
+ keys: PATTERN_KEYS,
20
+ now: Math.floor(Date.now() / 1000)
21
+ })
22
+ : undefined;
23
+ if (email === undefined)
24
+ return c.json({ error: 'Signed out' }, 401);
25
+ c.set('viewer', { userId: `pattern:${email}`, isAdmin: true });
26
+ return next();
27
+ }
@@ -5,7 +5,7 @@ async function inputOf(c, call) {
5
5
  const raw = call.method === 'get' ? c.req.query() : await c.req.json().catch(() => ({}));
6
6
  return call.input.safeParse(raw);
7
7
  }
8
- const BUILT_IN_PATHS = ['/auth', '/health', '/tables', '/app', '/me'];
8
+ const BUILT_IN_PATHS = ['/auth', '/health', '/tables', '/app', '/me', '/schema'];
9
9
  function builtInPathOf(path) {
10
10
  return BUILT_IN_PATHS.find((prefix) => path === prefix || path.startsWith(`${prefix}/`));
11
11
  }
@@ -1,4 +1,5 @@
1
1
  import type { SQLiteTableWithColumns } from 'drizzle-orm/sqlite-core';
2
+ import type { D1Database } from '@cloudflare/workers-types';
2
3
  import type { ServerEnv } from './env.ts';
3
4
  import type { Seed, Table, Tables } from '../table.ts';
4
5
  type SqlTable = SQLiteTableWithColumns<{
@@ -10,9 +11,22 @@ type SqlTable = SQLiteTableWithColumns<{
10
11
  export declare function sqlTable(table: Table): SqlTable;
11
12
  export declare function sqlTables<Group extends Tables>(tables: Group): Record<keyof Group, SqlTable>;
12
13
  export declare function seedSql<Group extends Tables>(tables: Group, seed: Seed<Group>): string;
14
+ export declare function readRows(d1: D1Database, table: Table): Promise<readonly Record<string, unknown>[]>;
15
+ export declare function readRow(d1: D1Database, table: Table, id: string): Promise<Record<string, unknown> | undefined>;
13
16
  export declare function serveTables<Group extends Tables>(tables: Group): import("hono/hono-base").HonoBase<ServerEnv, {
14
17
  "/:table": {
15
18
  $get: {
19
+ input: {
20
+ param: {
21
+ table: string;
22
+ };
23
+ };
24
+ output: readonly {
25
+ readonly [x: string]: import("hono/utils/types").JSONValue;
26
+ }[];
27
+ outputFormat: "json";
28
+ status: import("hono/utils/http-status").ContentfulStatusCode;
29
+ } | {
16
30
  input: {
17
31
  param: {
18
32
  table: string;
@@ -29,9 +43,11 @@ export declare function serveTables<Group extends Tables>(tables: Group): import
29
43
  table: string;
30
44
  };
31
45
  };
32
- output: {}[];
46
+ output: {
47
+ error: string;
48
+ };
33
49
  outputFormat: "json";
34
- status: import("hono/utils/http-status").ContentfulStatusCode;
50
+ status: 404;
35
51
  } | {
36
52
  input: {
37
53
  param: {
@@ -42,7 +58,7 @@ export declare function serveTables<Group extends Tables>(tables: Group): import
42
58
  error: string;
43
59
  };
44
60
  outputFormat: "json";
45
- status: 404;
61
+ status: 400;
46
62
  };
47
63
  };
48
64
  } & {
@@ -1,9 +1,10 @@
1
- import { asc, eq, sql } from 'drizzle-orm';
1
+ import { and, asc, eq, sql } from 'drizzle-orm';
2
2
  import { drizzle } from 'drizzle-orm/d1';
3
3
  import { integer, real, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
4
4
  import { Hono } from 'hono';
5
5
  import { z } from 'zod';
6
6
  import { changeRefusal, createRefusal, listRefusal, readScope } from "../access.js";
7
+ import { PAGE_PROBLEM, afterCursor, pageOf, pageReply } from "./keyset.js";
7
8
  import { ownerIdOf, seededRows, snakeCase } from "../table.js";
8
9
  function typesOf(schema) {
9
10
  const { type } = z.toJSONSchema(schema);
@@ -98,6 +99,29 @@ export function seedSql(tables, seed) {
98
99
  });
99
100
  return [...users, ...rowsSql].join('\n');
100
101
  }
102
+ function selection(table, target) {
103
+ return Object.fromEntries([
104
+ 'id',
105
+ 'createdAt',
106
+ ...(table.owned ? ['ownerId'] : []),
107
+ ...Object.keys(table.columns)
108
+ ].map((key) => [key, column(target, key)]));
109
+ }
110
+ export async function readRows(d1, table) {
111
+ const target = sqlTable(table);
112
+ return drizzle(d1)
113
+ .select(selection(table, target))
114
+ .from(target)
115
+ .orderBy(asc(column(target, 'createdAt')), asc(column(target, 'id')));
116
+ }
117
+ export async function readRow(d1, table, id) {
118
+ const target = sqlTable(table);
119
+ return drizzle(d1)
120
+ .select(selection(table, target))
121
+ .from(target)
122
+ .where(eq(column(target, 'id'), id))
123
+ .get();
124
+ }
101
125
  const REFUSAL_STATUSES = {
102
126
  signedOut: { status: 401, error: 'Signed out' },
103
127
  forbidden: { status: 403, error: 'forbidden' },
@@ -119,14 +143,6 @@ export function serveTables(tables) {
119
143
  table.name,
120
144
  { table, target: sqlTable(table) }
121
145
  ]));
122
- function selection(table, target) {
123
- return Object.fromEntries([
124
- 'id',
125
- 'createdAt',
126
- ...(table.owned ? ['ownerId'] : []),
127
- ...Object.keys(table.columns)
128
- ].map((key) => [key, column(target, key)]));
129
- }
130
146
  function publicSelection(table, target) {
131
147
  return Object.fromEntries(['id', ...table.public].map((key) => [key, column(target, key)]));
132
148
  }
@@ -161,13 +177,21 @@ export function serveTables(tables) {
161
177
  if (refusal !== undefined)
162
178
  return refuse(c, refusal);
163
179
  const scope = readScope(table, viewer);
180
+ const page = pageOf(c);
181
+ if (page === 'invalid') {
182
+ return c.json({ error: PAGE_PROBLEM }, 400);
183
+ }
164
184
  const query = drizzle(c.env.DB)
165
185
  .select(selection(table, target))
166
- .from(target);
167
- const rows = await (typeof scope === 'object'
168
- ? query.where(eq(column(target, 'ownerId'), scope.ownerId))
169
- : query).orderBy(asc(column(target, 'createdAt')), asc(column(target, 'id')));
170
- return c.json(rows);
186
+ .from(target)
187
+ .where(and(typeof scope === 'object'
188
+ ? eq(column(target, 'ownerId'), scope.ownerId)
189
+ : undefined, afterCursor(column(target, 'createdAt'), column(target, 'id'), page)))
190
+ .orderBy(asc(column(target, 'createdAt')), asc(column(target, 'id')));
191
+ const rows = await (page === undefined
192
+ ? query
193
+ : query.limit(page.limit + 1));
194
+ return pageReply(c, rows, page);
171
195
  })
172
196
  .get('/:table/public', async (c) => {
173
197
  const entry = known.get(c.req.param('table'));
@@ -25,11 +25,9 @@ export function httpServices(services, gate, onSignedOut) {
25
25
  return buildClient(services, (group, name, call) => (input = {}) => gate.pass(`${group}.${name}`, async () => {
26
26
  const { path, rest } = splitInput(call, input);
27
27
  const sent = call.input.parse(rest);
28
- const query = call.method === 'get' && Object.keys(sent).length > 0
29
- ? `?${new URLSearchParams(Object.entries(sent).map(([key, value]) => [
30
- key,
31
- String(value)
32
- ])).toString()}`
28
+ const fields = Object.entries(sent).filter(([, value]) => value !== undefined);
29
+ const query = call.method === 'get' && fields.length > 0
30
+ ? `?${new URLSearchParams(fields.map(([key, value]) => [key, String(value)])).toString()}`
33
31
  : '';
34
32
  const response = await fetch(`/api${path}${query}`, {
35
33
  method: call.method.toUpperCase(),
package/dist/start.js CHANGED
@@ -14,6 +14,7 @@ import { AppProvider } from "./hooks/appContext.js";
14
14
  import { routeObjects } from "./routes.js";
15
15
  import { borders, breakpoints, colors, controls, fontFeatures, fontSizes, fontWeights, fonts, iconSizes, lineHeights, radii, shadows, space, widths } from "./theme/tokens.stylex.js";
16
16
  const TEST_SEED = 'patternSeed';
17
+ const INLINE_DATA_ID = 'pattern-data';
17
18
  const TEST_ADMINS = 'patternAdmins';
18
19
  const TEST_WORLD = 'patternWorld';
19
20
  function testSeed() {
@@ -26,6 +27,22 @@ function testAdmins() {
26
27
  ? admins.filter((email) => typeof email === 'string')
27
28
  : [];
28
29
  }
30
+ function readInlineData() {
31
+ const text = document.getElementById(INLINE_DATA_ID)?.textContent ?? '';
32
+ if (text === '')
33
+ return undefined;
34
+ try {
35
+ const parsed = JSON.parse(text);
36
+ return typeof parsed === 'object' &&
37
+ parsed !== null &&
38
+ !Array.isArray(parsed)
39
+ ? parsed
40
+ : undefined;
41
+ }
42
+ catch {
43
+ return undefined;
44
+ }
45
+ }
29
46
  export function startApp(App, { theme = [], icons = {}, onInteraction } = {}) {
30
47
  setAppIcons(icons);
31
48
  document.body.className = stylex.props(base.body, theme).className ?? '';
@@ -48,7 +65,8 @@ export function startApp(App, { theme = [], icons = {}, onInteraction } = {}) {
48
65
  }),
49
66
  cache: Cache.local(),
50
67
  gate,
51
- clock: inMemory ? manualClock() : realClock()
68
+ clock: inMemory ? manualClock() : realClock(),
69
+ inline: inMemory ? undefined : readInlineData()
52
70
  });
53
71
  if (hot)
54
72
  exposeToDevTools(app, gate, hot, world);
@@ -81,12 +99,19 @@ function followAppearance(app) {
81
99
  }
82
100
  function followTitle(app) {
83
101
  const store = app.stores.app;
102
+ let renderedPath = document.getElementById(INLINE_DATA_ID) === null
103
+ ? undefined
104
+ : location.pathname;
84
105
  const apply = () => {
106
+ if (location.pathname === renderedPath)
107
+ return;
108
+ renderedPath = undefined;
85
109
  const { title } = store.currentState;
86
110
  if (title !== '')
87
111
  document.title = title;
88
112
  };
89
113
  store.subscribe(apply);
114
+ app.router.subscribe(apply);
90
115
  apply();
91
116
  }
92
117
  function exposeToDevTools(app, gate, hot, world) {
package/dist/table.d.ts CHANGED
@@ -137,6 +137,7 @@ type CheckedTable<Definition> = {
137
137
  export declare function defineTables<const Definitions extends Record<string, DraftTable> = Record<never, never>>(definitions: {
138
138
  [Key in keyof Definitions]: CheckedTable<Definitions[Key]>;
139
139
  }): TablesOf<Definitions>;
140
+ export declare function patternTable<const Name extends string, const Definition extends TableDefinition>(name: Name, definition: Definition): TableOf<Name, Definition>;
140
141
  export declare function ownerIdOf(email: string): string;
141
142
  export declare function seededRows(seed: Seed<Tables>, name: string): readonly Row<Table>[];
142
143
  export declare function snakeCase(name: string): string;
package/dist/table.js CHANGED
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  const NAME = /^[a-z][a-zA-Z0-9]*$/;
3
3
  const RESERVED = ['id', 'createdAt', 'ownerId'];
4
4
  const RESERVED_TABLES = ['users', 'sessions', 'session'];
5
+ const SDK_TABLES = ['media'];
5
6
  const SEEDED_AT = Date.UTC(2000, 0, 1);
6
7
  function problemIn(name, definition) {
7
8
  const { access, columns } = definition;
@@ -28,7 +29,7 @@ function problemIn(name, definition) {
28
29
  }
29
30
  return undefined;
30
31
  }
31
- function table(name, definition) {
32
+ function table(name, definition, isPatterns = false) {
32
33
  const { columns } = definition;
33
34
  for (const key of [name, ...Object.keys(columns)]) {
34
35
  if (!NAME.test(key)) {
@@ -38,6 +39,9 @@ function table(name, definition) {
38
39
  if (RESERVED_TABLES.includes(name)) {
39
40
  throw new Error(`A table can't be called "${name}": Pattern keeps its own ${name} for sign-in`);
40
41
  }
42
+ if (!isPatterns && SDK_TABLES.includes(name)) {
43
+ throw new Error(`A table can't be called "${name}": Pattern has its own ${name} table. Add it with import { ${name} } from '@trypattern/sdk/schema'`);
44
+ }
41
45
  const reserved = RESERVED.find((key) => key in columns);
42
46
  if (reserved !== undefined) {
43
47
  throw new Error(`Table "${name}" can't have a "${reserved}" column: Pattern adds it`);
@@ -75,6 +79,9 @@ function table(name, definition) {
75
79
  export function defineTables(definitions) {
76
80
  return Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [name, table(name, definition)]));
77
81
  }
82
+ export function patternTable(name, definition) {
83
+ return table(name, definition, true);
84
+ }
78
85
  export function ownerIdOf(email) {
79
86
  return `user-${email.toLowerCase()}`;
80
87
  }
package/dist/tables.d.ts CHANGED
@@ -64,10 +64,11 @@ type TableDeps<T extends Table> = {
64
64
  publicView?: {
65
65
  load: () => Promise<void>;
66
66
  };
67
+ inline?: unknown;
67
68
  };
68
69
  export declare class TableStore<T extends Table> extends EntityStore<Row<T>> {
69
70
  #private;
70
- constructor(table: T, { api, cache, viewer, publicView }: TableDeps<T>);
71
+ constructor(table: T, { api, cache, viewer, publicView, inline }: TableDeps<T>);
71
72
  get mayRead(): boolean;
72
73
  load(): Promise<void>;
73
74
  create(fields: Fields<T>): Promise<WriteOutcome>;
package/dist/tables.js CHANGED
@@ -172,14 +172,23 @@ export class PublicTableStore extends StoreBase {
172
172
  }
173
173
  }
174
174
  }
175
+ function inlineRows(table, inline) {
176
+ if (inline === undefined || table.access.read !== 'anyone')
177
+ return undefined;
178
+ const parsed = z.array(table.row).safeParse(inline);
179
+ return parsed.success
180
+ ? parsed.data
181
+ : undefined;
182
+ }
175
183
  export class TableStore extends EntityStore {
176
184
  #table;
177
185
  #viewer;
178
186
  #publicView;
179
- constructor(table, { api, cache, viewer, publicView }) {
187
+ constructor(table, { api, cache, viewer, publicView, inline }) {
180
188
  super({
181
189
  api,
182
190
  writes: table.writes,
191
+ initial: inlineRows(table, inline),
183
192
  cache: cache.scope(() => `${viewer()?.userId ?? 'anon'}:${table.name}`, z.array(table.row))
184
193
  });
185
194
  this.#table = table;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trypattern/sdk",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",