@trypattern/sdk 0.1.21 → 0.1.22

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.
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { appTitle } from './appTitle.ts';
3
3
  export { appTitle };
4
- export { ASSERTION_SECONDS, MEDIA_WRITE, NEXT_PAGE_HEADER, TABLES_READ, assertionKey, signAssertion } from './patternAccess.ts';
4
+ export { ASSERTION_SECONDS, MEDIA_WRITE, NEXT_PAGE_HEADER, TABLES_READ, TABLES_WRITE, assertionKey, signAssertion } from './patternAccess.ts';
5
5
  export type { AssertionKey, AssertionScope } from './patternAccess.ts';
6
6
  export declare const DASHBOARD_URL = "https://onpattern.app";
7
7
  export declare const APPS_DOMAIN = "onpattern.app";
@@ -320,6 +320,94 @@ export declare const databaseRows: z.ZodObject<{
320
320
  next: z.ZodNullable<z.ZodString>;
321
321
  }, z.core.$strip>;
322
322
  export type DatabaseRows = z.output<typeof databaseRows>;
323
+ export declare const CHANGE_SET_ROWS = 300;
324
+ export declare const dataChange: z.ZodDiscriminatedUnion<[z.ZodObject<{
325
+ op: z.ZodLiteral<"add">;
326
+ table: z.ZodString;
327
+ row: z.ZodRecord<z.ZodString, z.ZodUnknown>;
328
+ }, z.core.$strip>, z.ZodObject<{
329
+ op: z.ZodLiteral<"update">;
330
+ table: z.ZodString;
331
+ id: z.ZodString;
332
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
333
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
334
+ }, z.core.$strip>, z.ZodObject<{
335
+ op: z.ZodLiteral<"remove">;
336
+ table: z.ZodString;
337
+ id: z.ZodString;
338
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
339
+ }, z.core.$strip>], "op">;
340
+ export type DataChange = z.output<typeof dataChange>;
341
+ export declare const changeOutcome: z.ZodObject<{
342
+ status: z.ZodEnum<{
343
+ failed: "failed";
344
+ applied: "applied";
345
+ skipped: "skipped";
346
+ }>;
347
+ reason: z.ZodNullable<z.ZodString>;
348
+ row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
349
+ }, z.core.$strip>;
350
+ export type ChangeOutcome = z.output<typeof changeOutcome>;
351
+ export declare const changeReply: z.ZodObject<{
352
+ outcomes: z.ZodArray<z.ZodObject<{
353
+ status: z.ZodEnum<{
354
+ failed: "failed";
355
+ applied: "applied";
356
+ skipped: "skipped";
357
+ }>;
358
+ reason: z.ZodNullable<z.ZodString>;
359
+ row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
360
+ }, z.core.$strip>>;
361
+ }, z.core.$strip>;
362
+ export declare const changeSetStatus: z.ZodEnum<{
363
+ declined: "declined";
364
+ waiting: "waiting";
365
+ applied: "applied";
366
+ expired: "expired";
367
+ undone: "undone";
368
+ }>;
369
+ export declare const changeSet: z.ZodObject<{
370
+ id: z.ZodString;
371
+ status: z.ZodEnum<{
372
+ declined: "declined";
373
+ waiting: "waiting";
374
+ applied: "applied";
375
+ expired: "expired";
376
+ undone: "undone";
377
+ }>;
378
+ title: z.ZodString;
379
+ asked: z.ZodString;
380
+ device: z.ZodString;
381
+ createdAt: z.ZodString;
382
+ decidedAt: z.ZodNullable<z.ZodString>;
383
+ undoneAt: z.ZodNullable<z.ZodString>;
384
+ changes: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
385
+ op: z.ZodLiteral<"add">;
386
+ table: z.ZodString;
387
+ row: z.ZodRecord<z.ZodString, z.ZodUnknown>;
388
+ }, z.core.$strip>, z.ZodObject<{
389
+ op: z.ZodLiteral<"update">;
390
+ table: z.ZodString;
391
+ id: z.ZodString;
392
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
393
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
394
+ }, z.core.$strip>, z.ZodObject<{
395
+ op: z.ZodLiteral<"remove">;
396
+ table: z.ZodString;
397
+ id: z.ZodString;
398
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
399
+ }, z.core.$strip>], "op">>;
400
+ outcomes: z.ZodNullable<z.ZodArray<z.ZodObject<{
401
+ status: z.ZodEnum<{
402
+ failed: "failed";
403
+ applied: "applied";
404
+ skipped: "skipped";
405
+ }>;
406
+ reason: z.ZodNullable<z.ZodString>;
407
+ row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
408
+ }, z.core.$strip>>>;
409
+ }, z.core.$strip>;
410
+ export type ChangeSet = z.output<typeof changeSet>;
323
411
  export declare const publishServices: {
324
412
  readonly apps: {
325
413
  readonly list: NoInfer<import("./services.ts").ServiceCall<"/apps", {}, z.ZodArray<z.ZodObject<{
@@ -647,6 +735,272 @@ export declare const publishServices: {
647
735
  next: z.ZodNullable<z.ZodString>;
648
736
  }, z.core.$strip>>>;
649
737
  };
738
+ readonly changes: {
739
+ readonly list: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes", {}, z.ZodArray<z.ZodObject<{
740
+ id: z.ZodString;
741
+ status: z.ZodEnum<{
742
+ declined: "declined";
743
+ waiting: "waiting";
744
+ applied: "applied";
745
+ expired: "expired";
746
+ undone: "undone";
747
+ }>;
748
+ title: z.ZodString;
749
+ asked: z.ZodString;
750
+ device: z.ZodString;
751
+ createdAt: z.ZodString;
752
+ decidedAt: z.ZodNullable<z.ZodString>;
753
+ undoneAt: z.ZodNullable<z.ZodString>;
754
+ changes: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
755
+ op: z.ZodLiteral<"add">;
756
+ table: z.ZodString;
757
+ row: z.ZodRecord<z.ZodString, z.ZodUnknown>;
758
+ }, z.core.$strip>, z.ZodObject<{
759
+ op: z.ZodLiteral<"update">;
760
+ table: z.ZodString;
761
+ id: z.ZodString;
762
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
763
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
764
+ }, z.core.$strip>, z.ZodObject<{
765
+ op: z.ZodLiteral<"remove">;
766
+ table: z.ZodString;
767
+ id: z.ZodString;
768
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
769
+ }, z.core.$strip>], "op">>;
770
+ outcomes: z.ZodNullable<z.ZodArray<z.ZodObject<{
771
+ status: z.ZodEnum<{
772
+ failed: "failed";
773
+ applied: "applied";
774
+ skipped: "skipped";
775
+ }>;
776
+ reason: z.ZodNullable<z.ZodString>;
777
+ row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
778
+ }, z.core.$strip>>>;
779
+ }, z.core.$strip>>>>;
780
+ readonly read: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes/:id", {}, z.ZodObject<{
781
+ id: z.ZodString;
782
+ status: z.ZodEnum<{
783
+ declined: "declined";
784
+ waiting: "waiting";
785
+ applied: "applied";
786
+ expired: "expired";
787
+ undone: "undone";
788
+ }>;
789
+ title: z.ZodString;
790
+ asked: z.ZodString;
791
+ device: z.ZodString;
792
+ createdAt: z.ZodString;
793
+ decidedAt: z.ZodNullable<z.ZodString>;
794
+ undoneAt: z.ZodNullable<z.ZodString>;
795
+ changes: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
796
+ op: z.ZodLiteral<"add">;
797
+ table: z.ZodString;
798
+ row: z.ZodRecord<z.ZodString, z.ZodUnknown>;
799
+ }, z.core.$strip>, z.ZodObject<{
800
+ op: z.ZodLiteral<"update">;
801
+ table: z.ZodString;
802
+ id: z.ZodString;
803
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
804
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
805
+ }, z.core.$strip>, z.ZodObject<{
806
+ op: z.ZodLiteral<"remove">;
807
+ table: z.ZodString;
808
+ id: z.ZodString;
809
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
810
+ }, z.core.$strip>], "op">>;
811
+ outcomes: z.ZodNullable<z.ZodArray<z.ZodObject<{
812
+ status: z.ZodEnum<{
813
+ failed: "failed";
814
+ applied: "applied";
815
+ skipped: "skipped";
816
+ }>;
817
+ reason: z.ZodNullable<z.ZodString>;
818
+ row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
819
+ }, z.core.$strip>>>;
820
+ }, z.core.$strip>>>;
821
+ readonly create: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes", {
822
+ asked: z.ZodString;
823
+ changes: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
824
+ op: z.ZodLiteral<"add">;
825
+ table: z.ZodString;
826
+ row: z.ZodRecord<z.ZodString, z.ZodUnknown>;
827
+ }, z.core.$strip>, z.ZodObject<{
828
+ op: z.ZodLiteral<"update">;
829
+ table: z.ZodString;
830
+ id: z.ZodString;
831
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
832
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
833
+ }, z.core.$strip>, z.ZodObject<{
834
+ op: z.ZodLiteral<"remove">;
835
+ table: z.ZodString;
836
+ id: z.ZodString;
837
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
838
+ }, z.core.$strip>], "op">>;
839
+ }, z.ZodObject<{
840
+ id: z.ZodString;
841
+ status: z.ZodEnum<{
842
+ declined: "declined";
843
+ waiting: "waiting";
844
+ applied: "applied";
845
+ expired: "expired";
846
+ undone: "undone";
847
+ }>;
848
+ title: z.ZodString;
849
+ asked: z.ZodString;
850
+ device: z.ZodString;
851
+ createdAt: z.ZodString;
852
+ decidedAt: z.ZodNullable<z.ZodString>;
853
+ undoneAt: z.ZodNullable<z.ZodString>;
854
+ changes: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
855
+ op: z.ZodLiteral<"add">;
856
+ table: z.ZodString;
857
+ row: z.ZodRecord<z.ZodString, z.ZodUnknown>;
858
+ }, z.core.$strip>, z.ZodObject<{
859
+ op: z.ZodLiteral<"update">;
860
+ table: z.ZodString;
861
+ id: z.ZodString;
862
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
863
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
864
+ }, z.core.$strip>, z.ZodObject<{
865
+ op: z.ZodLiteral<"remove">;
866
+ table: z.ZodString;
867
+ id: z.ZodString;
868
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
869
+ }, z.core.$strip>], "op">>;
870
+ outcomes: z.ZodNullable<z.ZodArray<z.ZodObject<{
871
+ status: z.ZodEnum<{
872
+ failed: "failed";
873
+ applied: "applied";
874
+ skipped: "skipped";
875
+ }>;
876
+ reason: z.ZodNullable<z.ZodString>;
877
+ row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
878
+ }, z.core.$strip>>>;
879
+ }, z.core.$strip>>>;
880
+ readonly approve: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes/:id/approve", {}, z.ZodObject<{
881
+ id: z.ZodString;
882
+ status: z.ZodEnum<{
883
+ declined: "declined";
884
+ waiting: "waiting";
885
+ applied: "applied";
886
+ expired: "expired";
887
+ undone: "undone";
888
+ }>;
889
+ title: z.ZodString;
890
+ asked: z.ZodString;
891
+ device: z.ZodString;
892
+ createdAt: z.ZodString;
893
+ decidedAt: z.ZodNullable<z.ZodString>;
894
+ undoneAt: z.ZodNullable<z.ZodString>;
895
+ changes: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
896
+ op: z.ZodLiteral<"add">;
897
+ table: z.ZodString;
898
+ row: z.ZodRecord<z.ZodString, z.ZodUnknown>;
899
+ }, z.core.$strip>, z.ZodObject<{
900
+ op: z.ZodLiteral<"update">;
901
+ table: z.ZodString;
902
+ id: z.ZodString;
903
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
904
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
905
+ }, z.core.$strip>, z.ZodObject<{
906
+ op: z.ZodLiteral<"remove">;
907
+ table: z.ZodString;
908
+ id: z.ZodString;
909
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
910
+ }, z.core.$strip>], "op">>;
911
+ outcomes: z.ZodNullable<z.ZodArray<z.ZodObject<{
912
+ status: z.ZodEnum<{
913
+ failed: "failed";
914
+ applied: "applied";
915
+ skipped: "skipped";
916
+ }>;
917
+ reason: z.ZodNullable<z.ZodString>;
918
+ row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
919
+ }, z.core.$strip>>>;
920
+ }, z.core.$strip>>>;
921
+ readonly decline: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes/:id/decline", {}, z.ZodObject<{
922
+ id: z.ZodString;
923
+ status: z.ZodEnum<{
924
+ declined: "declined";
925
+ waiting: "waiting";
926
+ applied: "applied";
927
+ expired: "expired";
928
+ undone: "undone";
929
+ }>;
930
+ title: z.ZodString;
931
+ asked: z.ZodString;
932
+ device: z.ZodString;
933
+ createdAt: z.ZodString;
934
+ decidedAt: z.ZodNullable<z.ZodString>;
935
+ undoneAt: z.ZodNullable<z.ZodString>;
936
+ changes: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
937
+ op: z.ZodLiteral<"add">;
938
+ table: z.ZodString;
939
+ row: z.ZodRecord<z.ZodString, z.ZodUnknown>;
940
+ }, z.core.$strip>, z.ZodObject<{
941
+ op: z.ZodLiteral<"update">;
942
+ table: z.ZodString;
943
+ id: z.ZodString;
944
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
945
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
946
+ }, z.core.$strip>, z.ZodObject<{
947
+ op: z.ZodLiteral<"remove">;
948
+ table: z.ZodString;
949
+ id: z.ZodString;
950
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
951
+ }, z.core.$strip>], "op">>;
952
+ outcomes: z.ZodNullable<z.ZodArray<z.ZodObject<{
953
+ status: z.ZodEnum<{
954
+ failed: "failed";
955
+ applied: "applied";
956
+ skipped: "skipped";
957
+ }>;
958
+ reason: z.ZodNullable<z.ZodString>;
959
+ row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
960
+ }, z.core.$strip>>>;
961
+ }, z.core.$strip>>>;
962
+ readonly undo: NoInfer<import("./services.ts").ServiceCall<"/apps/:name/changes/:id/undo", {}, z.ZodObject<{
963
+ id: z.ZodString;
964
+ status: z.ZodEnum<{
965
+ declined: "declined";
966
+ waiting: "waiting";
967
+ applied: "applied";
968
+ expired: "expired";
969
+ undone: "undone";
970
+ }>;
971
+ title: z.ZodString;
972
+ asked: z.ZodString;
973
+ device: z.ZodString;
974
+ createdAt: z.ZodString;
975
+ decidedAt: z.ZodNullable<z.ZodString>;
976
+ undoneAt: z.ZodNullable<z.ZodString>;
977
+ changes: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
978
+ op: z.ZodLiteral<"add">;
979
+ table: z.ZodString;
980
+ row: z.ZodRecord<z.ZodString, z.ZodUnknown>;
981
+ }, z.core.$strip>, z.ZodObject<{
982
+ op: z.ZodLiteral<"update">;
983
+ table: z.ZodString;
984
+ id: z.ZodString;
985
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
986
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
987
+ }, z.core.$strip>, z.ZodObject<{
988
+ op: z.ZodLiteral<"remove">;
989
+ table: z.ZodString;
990
+ id: z.ZodString;
991
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
992
+ }, z.core.$strip>], "op">>;
993
+ outcomes: z.ZodNullable<z.ZodArray<z.ZodObject<{
994
+ status: z.ZodEnum<{
995
+ failed: "failed";
996
+ applied: "applied";
997
+ skipped: "skipped";
998
+ }>;
999
+ reason: z.ZodNullable<z.ZodString>;
1000
+ row: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1001
+ }, z.core.$strip>>>;
1002
+ }, z.core.$strip>>>;
1003
+ };
650
1004
  readonly logins: {
651
1005
  readonly read: NoInfer<import("./services.ts").ServiceCall<"/cli/logins/:approval", {}, z.ZodObject<{
652
1006
  code: z.ZodString;
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { appTitle } from "./appTitle.js";
3
3
  export { appTitle };
4
- export { ASSERTION_SECONDS, MEDIA_WRITE, NEXT_PAGE_HEADER, TABLES_READ, assertionKey, signAssertion } from "./patternAccess.js";
4
+ export { ASSERTION_SECONDS, MEDIA_WRITE, NEXT_PAGE_HEADER, TABLES_READ, TABLES_WRITE, assertionKey, signAssertion } from "./patternAccess.js";
5
5
  import { call, defineServices } from "./services.js";
6
6
  export const DASHBOARD_URL = 'https://onpattern.app';
7
7
  export const APPS_DOMAIN = 'onpattern.app';
@@ -256,6 +256,50 @@ export const databaseRows = z.object({
256
256
  rows: z.array(databaseRow),
257
257
  next: z.string().nullable()
258
258
  });
259
+ export const CHANGE_SET_ROWS = 300;
260
+ const tableName = z.string().regex(/^[a-zA-Z][a-zA-Z0-9]*$/);
261
+ const rowId = z.string().min(1).max(200);
262
+ export const dataChange = z.discriminatedUnion('op', [
263
+ z.object({ op: z.literal('add'), table: tableName, row: databaseRow }),
264
+ z.object({
265
+ op: z.literal('update'),
266
+ table: tableName,
267
+ id: rowId,
268
+ before: databaseRow,
269
+ after: databaseRow
270
+ }),
271
+ z.object({
272
+ op: z.literal('remove'),
273
+ table: tableName,
274
+ id: rowId,
275
+ before: databaseRow
276
+ })
277
+ ]);
278
+ export const changeOutcome = z.object({
279
+ status: z.enum(['applied', 'skipped', 'failed']),
280
+ reason: z.string().nullable(),
281
+ row: databaseRow.nullable()
282
+ });
283
+ export const changeReply = z.object({ outcomes: z.array(changeOutcome) });
284
+ export const changeSetStatus = z.enum([
285
+ 'waiting',
286
+ 'applied',
287
+ 'declined',
288
+ 'expired',
289
+ 'undone'
290
+ ]);
291
+ export const changeSet = z.object({
292
+ id: z.string(),
293
+ status: changeSetStatus,
294
+ title: z.string(),
295
+ asked: z.string(),
296
+ device: z.string(),
297
+ createdAt: z.string(),
298
+ decidedAt: z.string().nullable(),
299
+ undoneAt: z.string().nullable(),
300
+ changes: z.array(dataChange),
301
+ outcomes: z.array(changeOutcome).nullable()
302
+ });
259
303
  export const publishServices = defineServices({
260
304
  apps: {
261
305
  list: call({ get: '/apps', reply: z.array(appSummary) }),
@@ -332,6 +376,27 @@ export const publishServices = defineServices({
332
376
  reply: databaseRows
333
377
  })
334
378
  },
379
+ changes: {
380
+ list: call({ get: '/apps/:name/changes', reply: z.array(changeSet) }),
381
+ read: call({ get: '/apps/:name/changes/:id', reply: changeSet }),
382
+ create: call({
383
+ post: '/apps/:name/changes',
384
+ input: {
385
+ asked: z.string().trim().min(1).max(1000),
386
+ changes: z.array(dataChange).min(1).max(CHANGE_SET_ROWS)
387
+ },
388
+ reply: changeSet
389
+ }),
390
+ approve: call({
391
+ post: '/apps/:name/changes/:id/approve',
392
+ reply: changeSet
393
+ }),
394
+ decline: call({
395
+ post: '/apps/:name/changes/:id/decline',
396
+ reply: changeSet
397
+ }),
398
+ undo: call({ post: '/apps/:name/changes/:id/undo', reply: changeSet })
399
+ },
335
400
  logins: {
336
401
  read: call({ get: '/cli/logins/:approval', reply: loginToApprove }),
337
402
  approve: call({
@@ -0,0 +1,18 @@
1
+ import type { Context } from 'hono';
2
+ import type { Tables } from '../table.ts';
3
+ import type { ServerEnv } from './env.ts';
4
+ export declare function applyChanges<Group extends Tables>(c: Context<ServerEnv>, tables: Group): Promise<(Response & import("hono").TypedResponse<{
5
+ error: string;
6
+ }, 401, "json">) | (Response & import("hono").TypedResponse<{
7
+ error: string;
8
+ }, 403, "json">) | (Response & import("hono").TypedResponse<{
9
+ error: string;
10
+ }, 400, "json">) | (Response & import("hono").TypedResponse<{
11
+ outcomes: {
12
+ status: "failed" | "applied" | "skipped";
13
+ reason: string | null;
14
+ row: {
15
+ [x: string]: import("hono/utils/types").JSONValue;
16
+ } | null;
17
+ }[];
18
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
@@ -0,0 +1,113 @@
1
+ import { eq } from 'drizzle-orm';
2
+ import { drizzle } from 'drizzle-orm/d1';
3
+ import { z } from 'zod';
4
+ import { dataChange } from "../publishWire.js";
5
+ import { column, readRow, sqlTable } from "./tables.js";
6
+ const changeRequest = z.object({ changes: z.array(dataChange).max(1000) });
7
+ const META_KEYS = new Set(['id', 'createdAt', 'ownerId']);
8
+ function sameValue(left, right) {
9
+ return JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
10
+ }
11
+ function changedSince(current, before, keys) {
12
+ return keys.some((key) => !sameValue(current[key], before[key]));
13
+ }
14
+ function withoutMeta(row) {
15
+ return Object.fromEntries(Object.entries(row).filter(([key]) => !META_KEYS.has(key)));
16
+ }
17
+ function failed(reason) {
18
+ return { status: 'failed', reason, row: null };
19
+ }
20
+ function skipped(reason) {
21
+ return { status: 'skipped', reason, row: null };
22
+ }
23
+ function problemOf(failure) {
24
+ for (let error = failure; error instanceof Error; error = error.cause) {
25
+ if (error.message.includes('UNIQUE constraint failed')) {
26
+ return 'Another row already has that value';
27
+ }
28
+ }
29
+ return 'The site couldn’t save this row';
30
+ }
31
+ async function applyOne(c, table, change) {
32
+ const target = sqlTable(table);
33
+ const db = drizzle(c.env.DB);
34
+ if (change.op === 'add') {
35
+ if (table.owned)
36
+ return failed('Rows in this table belong to their makers');
37
+ const id = typeof change.row['id'] === 'string' && change.row['id'] !== ''
38
+ ? change.row['id']
39
+ : crypto.randomUUID();
40
+ const parsed = table.incoming.safeParse({ ...withoutMeta(change.row), id });
41
+ if (!parsed.success)
42
+ return failed(z.prettifyError(parsed.error));
43
+ const saved = await db
44
+ .insert(target)
45
+ .values(parsed.data)
46
+ .returning()
47
+ .get()
48
+ .catch((failure) => problemOf(failure));
49
+ if (typeof saved === 'string')
50
+ return failed(saved);
51
+ return {
52
+ status: 'applied',
53
+ reason: null,
54
+ row: (await readRow(c.env.DB, table, id)) ?? null
55
+ };
56
+ }
57
+ const current = await readRow(c.env.DB, table, change.id);
58
+ if (current === undefined)
59
+ return skipped('It was removed since');
60
+ if (change.op === 'remove') {
61
+ if (changedSince(current, change.before, Object.keys(change.before))) {
62
+ return skipped('It changed since');
63
+ }
64
+ await db.delete(target).where(eq(column(target, 'id'), change.id));
65
+ return { status: 'applied', reason: null, row: current };
66
+ }
67
+ const keys = Object.keys(withoutMeta(change.after));
68
+ if (changedSince(current, change.before, keys)) {
69
+ return skipped('It changed since');
70
+ }
71
+ const parsed = table.incoming.safeParse({
72
+ ...withoutMeta(current),
73
+ ...withoutMeta(change.after),
74
+ id: change.id
75
+ });
76
+ if (!parsed.success)
77
+ return failed(z.prettifyError(parsed.error));
78
+ const values = Object.fromEntries(keys.map((key) => [key, Reflect.get(parsed.data, key)]));
79
+ const saved = await db
80
+ .update(target)
81
+ .set(values)
82
+ .where(eq(column(target, 'id'), change.id))
83
+ .run()
84
+ .then(() => undefined)
85
+ .catch((failure) => problemOf(failure));
86
+ if (saved !== undefined)
87
+ return failed(saved);
88
+ return {
89
+ status: 'applied',
90
+ reason: null,
91
+ row: (await readRow(c.env.DB, table, change.id)) ?? null
92
+ };
93
+ }
94
+ export async function applyChanges(c, tables) {
95
+ const viewer = c.get('viewer');
96
+ if (viewer === null)
97
+ return c.json({ error: 'Signed out' }, 401);
98
+ if (!viewer.isAdmin)
99
+ return c.json({ error: 'forbidden' }, 403);
100
+ const body = changeRequest.safeParse(await c.req.json().catch(() => null));
101
+ if (!body.success) {
102
+ return c.json({ error: z.prettifyError(body.error) }, 400);
103
+ }
104
+ const known = new Map(Object.values(tables).map((table) => [table.name, table]));
105
+ const outcomes = [];
106
+ for (const change of body.data.changes) {
107
+ const table = known.get(change.table);
108
+ outcomes.push(table === undefined || table.access.read === 'server'
109
+ ? failed('No such table')
110
+ : await applyOne(c, table, change));
111
+ }
112
+ return c.json({ outcomes });
113
+ }
@@ -213,6 +213,44 @@ export declare function createServer<Group extends Tables, Bindings extends Serv
213
213
  status: import("hono/utils/http-status").ContentfulStatusCode;
214
214
  };
215
215
  };
216
+ } & {
217
+ "/changes": {
218
+ $post: {
219
+ input: {};
220
+ output: {
221
+ error: string;
222
+ };
223
+ outputFormat: "json";
224
+ status: 401;
225
+ } | {
226
+ input: {};
227
+ output: {
228
+ error: string;
229
+ };
230
+ outputFormat: "json";
231
+ status: 403;
232
+ } | {
233
+ input: {};
234
+ output: {
235
+ error: string;
236
+ };
237
+ outputFormat: "json";
238
+ status: 400;
239
+ } | {
240
+ input: {};
241
+ output: {
242
+ outcomes: {
243
+ status: "failed" | "applied" | "skipped";
244
+ reason: string | null;
245
+ row: {
246
+ [x: string]: import("hono/utils/types").JSONValue;
247
+ } | null;
248
+ }[];
249
+ };
250
+ outputFormat: "json";
251
+ status: import("hono/utils/http-status").ContentfulStatusCode;
252
+ };
253
+ };
216
254
  } & {
217
255
  "/tables/users": {
218
256
  $get: {
@@ -39,6 +39,44 @@ export declare function serveDatabase<Group extends Tables>(tables: Group): impo
39
39
  status: import("hono/utils/http-status").ContentfulStatusCode;
40
40
  };
41
41
  };
42
+ } & {
43
+ "/changes": {
44
+ $post: {
45
+ input: {};
46
+ output: {
47
+ error: string;
48
+ };
49
+ outputFormat: "json";
50
+ status: 401;
51
+ } | {
52
+ input: {};
53
+ output: {
54
+ error: string;
55
+ };
56
+ outputFormat: "json";
57
+ status: 403;
58
+ } | {
59
+ input: {};
60
+ output: {
61
+ error: string;
62
+ };
63
+ outputFormat: "json";
64
+ status: 400;
65
+ } | {
66
+ input: {};
67
+ output: {
68
+ outcomes: {
69
+ status: "failed" | "applied" | "skipped";
70
+ reason: string | null;
71
+ row: {
72
+ [x: string]: import("hono/utils/types").JSONValue;
73
+ } | null;
74
+ }[];
75
+ };
76
+ outputFormat: "json";
77
+ status: import("hono/utils/http-status").ContentfulStatusCode;
78
+ };
79
+ };
42
80
  } & {
43
81
  "/tables/users": {
44
82
  $get: {