instant-cli 0.22.156 → 0.22.157-branch-cli-query.22963839427.1

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,4 +1,4 @@
1
1
 
2
- > instant-cli@0.22.156 build /home/runner/work/instant/instant/client/packages/cli
2
+ > instant-cli@0.22.157-branch-cli-query.22963839427.1 build /home/runner/work/instant/instant/client/packages/cli
3
3
  > rm -rf dist; tsc -p tsconfig.json
4
4
 
@@ -1,7 +1,14 @@
1
1
  import { describe, it, expect } from 'vitest';
2
+ import { randomUUID } from 'crypto';
2
3
  import { readFile } from 'fs/promises';
3
4
  import { join } from 'path';
4
- import { runCli, createTestProject, createTempApp } from './helpers';
5
+ import {
6
+ runCli,
7
+ createTestProject,
8
+ createTempApp,
9
+ adminTransact,
10
+ createAppUser,
11
+ } from './helpers';
5
12
 
6
13
  const SCHEMA_FILE = `
7
14
  import { i } from "@instantdb/core";
@@ -494,6 +501,542 @@ export default _schema;
494
501
  });
495
502
  });
496
503
 
504
+ describe('query', () => {
505
+ it('queries data from an app', async () => {
506
+ const { appId, adminToken } = await createTempApp();
507
+ const project = await createTestProject({
508
+ appId,
509
+ schemaFile: SCHEMA_FILE,
510
+ });
511
+
512
+ try {
513
+ const pushResult = await runCli(['push', 'schema', '--yes'], {
514
+ cwd: project.dir,
515
+ env: {
516
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
517
+ INSTANT_APP_ID: appId,
518
+ },
519
+ });
520
+ expect(pushResult.exitCode).toBe(0);
521
+
522
+ await adminTransact(appId, adminToken, [
523
+ ['update', 'posts', randomUUID(), { title: 'Hello', body: 'World' }],
524
+ ['update', 'posts', randomUUID(), { title: 'Second', body: 'Post' }],
525
+ ]);
526
+
527
+ const result = await runCli(
528
+ ['query', '--admin', JSON.stringify({ posts: {} })],
529
+ {
530
+ cwd: project.dir,
531
+ env: {
532
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
533
+ INSTANT_APP_ID: appId,
534
+ },
535
+ },
536
+ );
537
+
538
+ expect(result.exitCode).toBe(0);
539
+ const data = JSON.parse(result.stdout);
540
+ expect(data.posts).toHaveLength(2);
541
+ expect(data.posts.map((p: any) => p.title).sort()).toEqual([
542
+ 'Hello',
543
+ 'Second',
544
+ ]);
545
+ } finally {
546
+ await project.cleanup();
547
+ }
548
+ });
549
+
550
+ it('queries with filters', async () => {
551
+ const { appId, adminToken } = await createTempApp();
552
+ const project = await createTestProject({
553
+ appId,
554
+ schemaFile: SCHEMA_FILE,
555
+ });
556
+
557
+ try {
558
+ const pushResult = await runCli(['push', 'schema', '--yes'], {
559
+ cwd: project.dir,
560
+ env: {
561
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
562
+ INSTANT_APP_ID: appId,
563
+ },
564
+ });
565
+ expect(pushResult.exitCode).toBe(0);
566
+
567
+ await adminTransact(appId, adminToken, [
568
+ ['update', 'posts', randomUUID(), { title: 'Match', body: 'Yes' }],
569
+ ['update', 'posts', randomUUID(), { title: 'Nope', body: 'No' }],
570
+ ]);
571
+
572
+ const query = {
573
+ posts: { $: { where: { title: 'Match' } } },
574
+ };
575
+ const result = await runCli(
576
+ ['query', '--admin', JSON.stringify(query)],
577
+ {
578
+ cwd: project.dir,
579
+ env: {
580
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
581
+ INSTANT_APP_ID: appId,
582
+ },
583
+ },
584
+ );
585
+
586
+ expect(result.exitCode).toBe(0);
587
+ const data = JSON.parse(result.stdout);
588
+ expect(data.posts).toHaveLength(1);
589
+ expect(data.posts[0].title).toBe('Match');
590
+ } finally {
591
+ await project.cleanup();
592
+ }
593
+ });
594
+
595
+ it('returns empty arrays for entities with no data', async () => {
596
+ const { appId, adminToken } = await createTempApp();
597
+ const project = await createTestProject({
598
+ appId,
599
+ schemaFile: SCHEMA_FILE,
600
+ });
601
+
602
+ try {
603
+ const pushResult = await runCli(['push', 'schema', '--yes'], {
604
+ cwd: project.dir,
605
+ env: {
606
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
607
+ INSTANT_APP_ID: appId,
608
+ },
609
+ });
610
+ expect(pushResult.exitCode).toBe(0);
611
+
612
+ const result = await runCli(
613
+ ['query', '--admin', JSON.stringify({ posts: {} })],
614
+ {
615
+ cwd: project.dir,
616
+ env: {
617
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
618
+ INSTANT_APP_ID: appId,
619
+ },
620
+ },
621
+ );
622
+
623
+ expect(result.exitCode).toBe(0);
624
+ const data = JSON.parse(result.stdout);
625
+ expect(data.posts).toEqual([]);
626
+ } finally {
627
+ await project.cleanup();
628
+ }
629
+ });
630
+
631
+ it('works with --app flag', async () => {
632
+ const { appId, adminToken } = await createTempApp();
633
+ const project = await createTestProject({
634
+ schemaFile: SCHEMA_FILE,
635
+ });
636
+
637
+ try {
638
+ const pushResult = await runCli(
639
+ ['push', 'schema', '--app', appId, '--yes'],
640
+ {
641
+ cwd: project.dir,
642
+ env: { INSTANT_CLI_AUTH_TOKEN: adminToken },
643
+ },
644
+ );
645
+ expect(pushResult.exitCode).toBe(0);
646
+
647
+ const result = await runCli(
648
+ ['query', '--admin', '--app', appId, JSON.stringify({ posts: {} })],
649
+ {
650
+ cwd: project.dir,
651
+ env: { INSTANT_CLI_AUTH_TOKEN: adminToken },
652
+ },
653
+ );
654
+
655
+ expect(result.exitCode).toBe(0);
656
+ const data = JSON.parse(result.stdout);
657
+ expect(data.posts).toEqual([]);
658
+ } finally {
659
+ await project.cleanup();
660
+ }
661
+ });
662
+
663
+ it('fails with invalid JSON', async () => {
664
+ const { appId, adminToken } = await createTempApp();
665
+ const project = await createTestProject({ appId });
666
+
667
+ try {
668
+ const result = await runCli(['query', '--admin', 'not valid json'], {
669
+ cwd: project.dir,
670
+ env: {
671
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
672
+ INSTANT_APP_ID: appId,
673
+ },
674
+ });
675
+
676
+ expect(result.exitCode).not.toBe(0);
677
+ } finally {
678
+ await project.cleanup();
679
+ }
680
+ });
681
+
682
+ it('accepts JSON5 syntax (unquoted keys)', async () => {
683
+ const { appId, adminToken } = await createTempApp();
684
+ const project = await createTestProject({ appId });
685
+
686
+ try {
687
+ const result = await runCli(['query', '--admin', '{ $users: {} }'], {
688
+ cwd: project.dir,
689
+ env: {
690
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
691
+ INSTANT_APP_ID: appId,
692
+ },
693
+ });
694
+
695
+ expect(result.exitCode).toBe(0);
696
+ const data = JSON.parse(result.stdout);
697
+ expect(data.$users).toEqual([]);
698
+ } finally {
699
+ await project.cleanup();
700
+ }
701
+ });
702
+
703
+ it('fails without context flag', async () => {
704
+ const { appId, adminToken } = await createTempApp();
705
+ const project = await createTestProject({ appId });
706
+
707
+ try {
708
+ const result = await runCli(['query', JSON.stringify({ posts: {} })], {
709
+ cwd: project.dir,
710
+ env: {
711
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
712
+ INSTANT_APP_ID: appId,
713
+ },
714
+ });
715
+
716
+ expect(result.exitCode).not.toBe(0);
717
+ const output = result.stdout + result.stderr;
718
+ expect(output).toMatch(/--admin|--as-email|--as-guest/);
719
+ } finally {
720
+ await project.cleanup();
721
+ }
722
+ });
723
+
724
+ it('fails with multiple context flags', async () => {
725
+ const { appId, adminToken } = await createTempApp();
726
+ const project = await createTestProject({ appId });
727
+
728
+ try {
729
+ const result = await runCli(
730
+ [
731
+ 'query',
732
+ '--as-email',
733
+ 'alice@example.com',
734
+ '--as-guest',
735
+ JSON.stringify({ posts: {} }),
736
+ ],
737
+ {
738
+ cwd: project.dir,
739
+ env: {
740
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
741
+ INSTANT_APP_ID: appId,
742
+ },
743
+ },
744
+ );
745
+
746
+ expect(result.exitCode).not.toBe(0);
747
+ const output = result.stdout + result.stderr;
748
+ expect(output).toMatch(/exactly one context/);
749
+ } finally {
750
+ await project.cleanup();
751
+ }
752
+ });
753
+
754
+ it('fails without auth', async () => {
755
+ const { appId } = await createTempApp();
756
+ const project = await createTestProject({ appId });
757
+
758
+ try {
759
+ const result = await runCli(
760
+ ['query', '--admin', JSON.stringify({ posts: {} })],
761
+ {
762
+ cwd: project.dir,
763
+ env: {
764
+ INSTANT_CLI_AUTH_TOKEN: '',
765
+ INSTANT_APP_ADMIN_TOKEN: '',
766
+ INSTANT_ADMIN_TOKEN: '',
767
+ INSTANT_APP_ID: appId,
768
+ },
769
+ },
770
+ );
771
+
772
+ expect(result.exitCode).not.toBe(0);
773
+ const output = result.stdout + result.stderr;
774
+ expect(output).toMatch(/not logged in/i);
775
+ } finally {
776
+ await project.cleanup();
777
+ }
778
+ });
779
+
780
+ it('queries nested associations', async () => {
781
+ const { appId, adminToken } = await createTempApp();
782
+ const project = await createTestProject({
783
+ appId,
784
+ schemaFile: SCHEMA_FILE,
785
+ });
786
+
787
+ try {
788
+ const pushResult = await runCli(['push', 'schema', '--yes'], {
789
+ cwd: project.dir,
790
+ env: {
791
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
792
+ INSTANT_APP_ID: appId,
793
+ },
794
+ });
795
+ expect(pushResult.exitCode).toBe(0);
796
+
797
+ const postId = randomUUID();
798
+ const commentId = randomUUID();
799
+ await adminTransact(appId, adminToken, [
800
+ ['update', 'posts', postId, { title: 'My Post', body: 'Content' }],
801
+ ['update', 'comments', commentId, { text: 'Great post!' }],
802
+ ['link', 'posts', postId, { comments: commentId }],
803
+ ]);
804
+
805
+ const result = await runCli(
806
+ ['query', '--admin', JSON.stringify({ posts: { comments: {} } })],
807
+ {
808
+ cwd: project.dir,
809
+ env: {
810
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
811
+ INSTANT_APP_ID: appId,
812
+ },
813
+ },
814
+ );
815
+
816
+ expect(result.exitCode).toBe(0);
817
+ const data = JSON.parse(result.stdout);
818
+ expect(data.posts).toHaveLength(1);
819
+ expect(data.posts[0].comments).toHaveLength(1);
820
+ expect(data.posts[0].comments[0].text).toBe('Great post!');
821
+ } finally {
822
+ await project.cleanup();
823
+ }
824
+ });
825
+
826
+ it('--as-email runs query as a specific user with perms applied', async () => {
827
+ const { appId, adminToken } = await createTempApp();
828
+
829
+ const schemaWithCreator = `
830
+ import { i } from "@instantdb/core";
831
+ const _schema = i.schema({
832
+ entities: {
833
+ posts: i.entity({
834
+ title: i.string(),
835
+ creatorEmail: i.string(),
836
+ }),
837
+ },
838
+ });
839
+ export default _schema;
840
+ `;
841
+
842
+ // Only allow users to view posts they created
843
+ const restrictedPerms = `export default {
844
+ posts: {
845
+ allow: {
846
+ view: "auth.email == data.creatorEmail",
847
+ },
848
+ },
849
+ };
850
+ `;
851
+
852
+ const project = await createTestProject({
853
+ appId,
854
+ schemaFile: schemaWithCreator,
855
+ permsFile: restrictedPerms,
856
+ });
857
+
858
+ try {
859
+ const pushResult = await runCli(['push', '--yes'], {
860
+ cwd: project.dir,
861
+ env: {
862
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
863
+ INSTANT_APP_ID: appId,
864
+ },
865
+ });
866
+ expect(pushResult.exitCode).toBe(0);
867
+
868
+ await createAppUser(appId, adminToken, 'alice@test.com');
869
+ await createAppUser(appId, adminToken, 'bob@test.com');
870
+
871
+ await adminTransact(appId, adminToken, [
872
+ [
873
+ 'update',
874
+ 'posts',
875
+ randomUUID(),
876
+ { title: "Alice's Post", creatorEmail: 'alice@test.com' },
877
+ ],
878
+ [
879
+ 'update',
880
+ 'posts',
881
+ randomUUID(),
882
+ { title: "Bob's Post", creatorEmail: 'bob@test.com' },
883
+ ],
884
+ ]);
885
+
886
+ // Admin sees all posts
887
+ const adminResult = await runCli(
888
+ ['query', '--admin', JSON.stringify({ posts: {} })],
889
+ {
890
+ cwd: project.dir,
891
+ env: {
892
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
893
+ INSTANT_APP_ID: appId,
894
+ },
895
+ },
896
+ );
897
+ expect(adminResult.exitCode).toBe(0);
898
+ const adminData = JSON.parse(adminResult.stdout);
899
+ expect(adminData.posts).toHaveLength(2);
900
+
901
+ // Alice only sees her post
902
+ const aliceResult = await runCli(
903
+ [
904
+ 'query',
905
+ '--as-email',
906
+ 'alice@test.com',
907
+ JSON.stringify({ posts: {} }),
908
+ ],
909
+ {
910
+ cwd: project.dir,
911
+ env: {
912
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
913
+ INSTANT_APP_ID: appId,
914
+ },
915
+ },
916
+ );
917
+ expect(aliceResult.exitCode).toBe(0);
918
+ const aliceData = JSON.parse(aliceResult.stdout);
919
+ expect(aliceData.posts).toHaveLength(1);
920
+ expect(aliceData.posts[0].title).toBe("Alice's Post");
921
+
922
+ // Bob only sees his post
923
+ const bobResult = await runCli(
924
+ [
925
+ 'query',
926
+ '--as-email',
927
+ 'bob@test.com',
928
+ JSON.stringify({ posts: {} }),
929
+ ],
930
+ {
931
+ cwd: project.dir,
932
+ env: {
933
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
934
+ INSTANT_APP_ID: appId,
935
+ },
936
+ },
937
+ );
938
+ expect(bobResult.exitCode).toBe(0);
939
+ const bobData = JSON.parse(bobResult.stdout);
940
+ expect(bobData.posts).toHaveLength(1);
941
+ expect(bobData.posts[0].title).toBe("Bob's Post");
942
+ } finally {
943
+ await project.cleanup();
944
+ }
945
+ });
946
+
947
+ it('--as-guest runs query as unauthenticated user', async () => {
948
+ const { appId, adminToken } = await createTempApp();
949
+
950
+ // Guests can only see posts marked as public
951
+ const schemaWithVisibility = `
952
+ import { i } from "@instantdb/core";
953
+ const _schema = i.schema({
954
+ entities: {
955
+ posts: i.entity({
956
+ title: i.string(),
957
+ isPublic: i.boolean(),
958
+ }),
959
+ },
960
+ });
961
+ export default _schema;
962
+ `;
963
+
964
+ const guestPerms = `export default {
965
+ posts: {
966
+ allow: {
967
+ view: "data.isPublic == true",
968
+ },
969
+ },
970
+ };
971
+ `;
972
+
973
+ const project = await createTestProject({
974
+ appId,
975
+ schemaFile: schemaWithVisibility,
976
+ permsFile: guestPerms,
977
+ });
978
+
979
+ try {
980
+ const pushResult = await runCli(['push', '--yes'], {
981
+ cwd: project.dir,
982
+ env: {
983
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
984
+ INSTANT_APP_ID: appId,
985
+ },
986
+ });
987
+ expect(pushResult.exitCode).toBe(0);
988
+
989
+ await adminTransact(appId, adminToken, [
990
+ [
991
+ 'update',
992
+ 'posts',
993
+ randomUUID(),
994
+ { title: 'Public Post', isPublic: true },
995
+ ],
996
+ [
997
+ 'update',
998
+ 'posts',
999
+ randomUUID(),
1000
+ { title: 'Private Post', isPublic: false },
1001
+ ],
1002
+ ]);
1003
+
1004
+ // Admin sees both
1005
+ const adminResult = await runCli(
1006
+ ['query', '--admin', JSON.stringify({ posts: {} })],
1007
+ {
1008
+ cwd: project.dir,
1009
+ env: {
1010
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
1011
+ INSTANT_APP_ID: appId,
1012
+ },
1013
+ },
1014
+ );
1015
+ expect(adminResult.exitCode).toBe(0);
1016
+ const adminData = JSON.parse(adminResult.stdout);
1017
+ expect(adminData.posts).toHaveLength(2);
1018
+
1019
+ // Guest only sees public post
1020
+ const guestResult = await runCli(
1021
+ ['query', '--as-guest', JSON.stringify({ posts: {} })],
1022
+ {
1023
+ cwd: project.dir,
1024
+ env: {
1025
+ INSTANT_CLI_AUTH_TOKEN: adminToken,
1026
+ INSTANT_APP_ID: appId,
1027
+ },
1028
+ },
1029
+ );
1030
+ expect(guestResult.exitCode).toBe(0);
1031
+ const guestData = JSON.parse(guestResult.stdout);
1032
+ expect(guestData.posts).toHaveLength(1);
1033
+ expect(guestData.posts[0].title).toBe('Public Post');
1034
+ } finally {
1035
+ await project.cleanup();
1036
+ }
1037
+ });
1038
+ });
1039
+
497
1040
  describe('info', () => {
498
1041
  it('shows version', async () => {
499
1042
  const result = await runCli(['info']);
@@ -114,6 +114,50 @@ export async function createTestProject(
114
114
  };
115
115
  }
116
116
 
117
+ export async function adminTransact(
118
+ appId: string,
119
+ adminToken: string,
120
+ steps: any[],
121
+ ): Promise<void> {
122
+ const response = await fetch(`${apiUrl}/admin/transact`, {
123
+ method: 'POST',
124
+ headers: {
125
+ 'Content-Type': 'application/json',
126
+ Authorization: `Bearer ${adminToken}`,
127
+ 'app-id': appId,
128
+ },
129
+ body: JSON.stringify({ steps }),
130
+ });
131
+ if (!response.ok) {
132
+ throw new Error(
133
+ `Failed to transact: ${response.status} ${await response.text()}`,
134
+ );
135
+ }
136
+ }
137
+
138
+ export async function createAppUser(
139
+ appId: string,
140
+ adminToken: string,
141
+ email: string,
142
+ ): Promise<{ userId: string; refreshToken: string }> {
143
+ const response = await fetch(`${apiUrl}/admin/refresh_tokens`, {
144
+ method: 'POST',
145
+ headers: {
146
+ 'Content-Type': 'application/json',
147
+ Authorization: `Bearer ${adminToken}`,
148
+ 'app-id': appId,
149
+ },
150
+ body: JSON.stringify({ email }),
151
+ });
152
+ if (!response.ok) {
153
+ throw new Error(
154
+ `Failed to create user: ${response.status} ${await response.text()}`,
155
+ );
156
+ }
157
+ const data = await response.json();
158
+ return { userId: data.user.id, refreshToken: data.user.refresh_token };
159
+ }
160
+
117
161
  export async function createTempApp(title = 'cli-e2e-test'): Promise<{
118
162
  appId: string;
119
163
  adminToken: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAwhEA,8EAQC;AA+ED;;;;;EAKE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAgnEA,8EAQC;AA+ED;;;;;EAKE"}