foobara-typescript-remote-command-generator 0.0.20 → 0.0.21

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4528c916d3984695d98001864a1ab16ee6b71413d4602b86fa98691197462603
4
- data.tar.gz: 15e85d4edb9cafde3c3bbf198f6e1a8a9801ba7123228eb18320eb74d291c23b
3
+ metadata.gz: 894c3cb3bb470bc2ae517516af5eadf72271a842d91b4d9d3091df927a59f7ef
4
+ data.tar.gz: d78ee07ebf763f5c81459f5eb20c27e30b7f99f36975258e938fef80fb8f2e82
5
5
  SHA512:
6
- metadata.gz: 47432f9b6da3f579bf74a94201e3bef097c0bfe928fafd3d334a9e9f9e7aa0465a7b65e827ae44d9a42d01391caee0637bd5d6ad6e5e9e2c80f9c21623561bf4
7
- data.tar.gz: c2510181c61cc4bd7b6126d95b8edebb33ef5c27f50ee9ce76a76da5a64ecc9894dd0226122503f4d33099a2d50c7ea64e5727f6609a40a21b5368af34108628
6
+ metadata.gz: '058e5c94c619269433b72e3ef51caf0eebbe808420334af00482d234bdfc153ab3dc40946c023e6cd65a77f1ee3e56bf2323b256981990403ad928d1ec9bfb25'
7
+ data.tar.gz: 4b83e9a472967eed6c6aea176f0b966374aa1f688d17a5c6c7a46f45849a499f34dcb3489a24173f7b7d11a35c5f348763158afa34abb2391df7265e79de1847
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [0.0.21] - 2025-05-13
2
+
3
+ - Implement Query/QueryCache/useQuery to use commands as data sources and share that data across components
4
+
1
5
  ## [0.0.20] - 2025-05-03
2
6
 
3
7
  - Add a potentially missing require_relative
@@ -0,0 +1,19 @@
1
+ module Foobara
2
+ module RemoteGenerator
3
+ class Services
4
+ module Auth
5
+ class SetupGenerator < TypescriptFromManifestBaseGenerator
6
+ def applicable?
7
+ relevant_manifest.commands.any? do |command_manifest|
8
+ command_manifest.full_command_name =~ /\bGetCurrentUser$/
9
+ end
10
+ end
11
+
12
+ def template_path
13
+ "setup.ts.erb"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -80,7 +80,10 @@ module Foobara
80
80
  when Manifest::ProcessorClass
81
81
  Services::ProcessorClassGenerator
82
82
  when Manifest::RootManifest
83
- Services::RootManifestGenerator
83
+ [
84
+ Services::RootManifestGenerator,
85
+ Services::Auth::SetupGenerator
86
+ ]
84
87
  when Manifest::Type
85
88
  Services::TypeGenerator
86
89
  else
@@ -59,6 +59,7 @@ module Foobara
59
59
 
60
60
  def run_post_generation_tasks
61
61
  eslint_fix
62
+ warn_about_adding_setup_to_index
62
63
  end
63
64
 
64
65
  def eslint_fix
@@ -73,6 +74,13 @@ module Foobara
73
74
  end
74
75
  end
75
76
  end
77
+
78
+ def warn_about_adding_setup_to_index
79
+ if paths_to_source_code.key?("setup.ts")
80
+ warn "WARNING: you should add the following to src/index.ts:\n\n" \
81
+ "import './domains/setup'"
82
+ end
83
+ end
76
84
  end
77
85
  end
78
86
  end
@@ -1,11 +1,31 @@
1
+ import type Query from '../../../base/Query'
2
+ import type RemoteCommand from '../../../base/RemoteCommand'
3
+
4
+ let getCurrentUserQuery: Query<RemoteCommand<any, any, any>> | undefined
5
+
6
+ export function setGetCurrentUserQuery (query: Query<RemoteCommand<any, any, any>>): void {
7
+ getCurrentUserQuery = query
8
+ }
9
+
10
+ function setCurrentUserDirty () {
11
+ console.log('setCurrentUserDirty')
12
+ if (getCurrentUserQuery != null) {
13
+ getCurrentUserQuery.setDirty()
14
+ }
15
+ }
16
+
1
17
  const accessTokens = new Map<string, string>()
2
18
 
3
- const logout = (urlBase: string): void => { accessTokens.delete(urlBase) }
19
+ const logout = (urlBase: string): void => {
20
+ accessTokens.delete(urlBase)
21
+ setCurrentUserDirty()
22
+ }
4
23
  let handleLogout: (baseUrl: string) => void = logout
5
24
 
6
25
  const tokenForUrl = (baseUrl: string): string | undefined => accessTokens.get(baseUrl)
7
26
  const handleLogin: (baseUrl: string, accessToken: string) => void = (baseUrl, accessToken) => {
8
27
  accessTokens.set(baseUrl, accessToken)
28
+ setCurrentUserDirty()
9
29
  }
10
30
 
11
31
  if (typeof BroadcastChannel !== 'undefined') {
@@ -0,0 +1,125 @@
1
+ import type RemoteCommand from '../base/RemoteCommand'
2
+ import { type Outcome } from '../base/Outcome'
3
+ import {
4
+ type InputsOf, type ResultOf, type ErrorOf, type RemoteCommandConstructor
5
+ } from './RemoteCommandTypes'
6
+
7
+ export default class Query<CommandT extends RemoteCommand<any, any, any>> {
8
+ inputs: InputsOf<CommandT>
9
+ CommandClass: RemoteCommandConstructor<CommandT>
10
+ command: CommandT | undefined
11
+ isLoading: boolean = false
12
+ isFailure: boolean = false
13
+ isDirty: boolean = false
14
+ ranWhileRunning: boolean = false
15
+ listeners: Array<() => void> = []
16
+ inputsChangedListeners: Array<() => void> = []
17
+ failure: any
18
+
19
+ constructor (
20
+ CommandClass: RemoteCommandConstructor<CommandT>,
21
+ inputs: InputsOf<CommandT> | undefined = undefined
22
+ ) {
23
+ this.CommandClass = CommandClass
24
+ this.inputs = inputs ?? (undefined as unknown as InputsOf<CommandT>)
25
+ }
26
+
27
+ get outcome (): null | Outcome<ResultOf<CommandT>, ErrorOf<CommandT>> {
28
+ return this.command?.outcome ?? null
29
+ }
30
+
31
+ get result (): null | ResultOf<CommandT> {
32
+ return this.command?.outcome?.result ?? null
33
+ }
34
+
35
+ get errors (): null | Array<ErrorOf<CommandT>> {
36
+ return this.command?.outcome?.errors ?? null
37
+ }
38
+
39
+ onChange (callback: () => void): () => void {
40
+ this.listeners.push(callback)
41
+ return () => {
42
+ const index = this.listeners.indexOf(callback)
43
+ if (index !== -1) {
44
+ this.listeners.splice(index, 1)
45
+ }
46
+ }
47
+ }
48
+
49
+ onInputsChange (callback: () => void): () => void {
50
+ this.inputsChangedListeners.push(callback)
51
+
52
+ return () => {
53
+ const index = this.inputsChangedListeners.indexOf(callback)
54
+ if (index !== -1) {
55
+ this.inputsChangedListeners.splice(index, 1)
56
+ }
57
+ }
58
+ }
59
+
60
+ fireChange (): void {
61
+ this.listeners.forEach(listener => { listener() })
62
+ }
63
+
64
+ fireInputsChanged (): void {
65
+ this.inputsChangedListeners.forEach(listener => { listener() })
66
+ }
67
+
68
+ setInputs (inputs: InputsOf<CommandT>): void {
69
+ this.inputs = inputs
70
+ this.fireInputsChanged()
71
+ this.setDirty()
72
+ }
73
+
74
+ setDirty (): void {
75
+ this.isDirty = true
76
+ this.run()
77
+ }
78
+
79
+ get isSuccess (): boolean {
80
+ return !this.isFailure && (this.command?.outcome?.isSuccess() ?? false)
81
+ }
82
+
83
+ get isError (): boolean {
84
+ if (this.command?.outcome == null) {
85
+ return false
86
+ }
87
+ return !this.command.outcome.isSuccess()
88
+ }
89
+
90
+ get isPending (): boolean {
91
+ return !this.isLoading || !this.isFailure || this.command == null
92
+ }
93
+
94
+ async run (): Promise<void> {
95
+ if (this.isLoading) {
96
+ this.ranWhileRunning = true
97
+ this.fireChange()
98
+ return
99
+ }
100
+
101
+ try {
102
+ this.isLoading = true
103
+ const command = new this.CommandClass(this.inputs)
104
+ this.fireChange()
105
+
106
+ await command.run()
107
+
108
+ this.isFailure = false
109
+ this.isDirty = false
110
+ this.command = command
111
+ this.failure = undefined
112
+ } catch (error) {
113
+ this.isFailure = true
114
+ this.failure = error
115
+ throw error
116
+ } finally {
117
+ this.isLoading = false
118
+ this.fireChange()
119
+ if (this.ranWhileRunning) {
120
+ this.ranWhileRunning = false
121
+ this.run()
122
+ }
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,47 @@
1
+ import type RemoteCommand from '../base/RemoteCommand'
2
+ import Query from '../base/Query'
3
+ import { type InputsOf, type RemoteCommandConstructor } from '../base/RemoteCommandTypes'
4
+
5
+ const queryCache = new Map<string, Query<RemoteCommand<any, any, any>>>()
6
+
7
+ export function getQuery<CommandT extends RemoteCommand<any, any, any>> (
8
+ CommandClass: RemoteCommandConstructor<CommandT>,
9
+ inputs: InputsOf<CommandT> | undefined = undefined
10
+ ): Query<CommandT> {
11
+ const key: string = toKey(CommandClass, inputs)
12
+ const hit = queryCache.get(key)
13
+
14
+ if (hit != null) {
15
+ return (hit as unknown as Query<CommandT>)
16
+ }
17
+
18
+ let query: Query<CommandT>
19
+ if (arguments.length === 2) {
20
+ query = new Query<CommandT>(CommandClass, inputs)
21
+ query.run()
22
+ } else {
23
+ query = new Query<CommandT>(CommandClass)
24
+ }
25
+
26
+ query.onInputsChange(() => {
27
+ queryCache.delete(key)
28
+ const newKey = toKey(CommandClass, query.inputs)
29
+ queryCache.set(newKey, query)
30
+ })
31
+
32
+ queryCache.set(key, query)
33
+ return query
34
+ }
35
+
36
+ function toKey<CommandT extends RemoteCommand<any, any, any>> (
37
+ CommandClass: RemoteCommandConstructor<CommandT>,
38
+ inputs: InputsOf<CommandT> | undefined
39
+ ): string {
40
+ let key: string = CommandClass.fullCommandName
41
+
42
+ if (inputs != null) {
43
+ key += JSON.stringify(inputs)
44
+ }
45
+
46
+ return key
47
+ }
@@ -0,0 +1,10 @@
1
+ import type RemoteCommand from './RemoteCommand'
2
+
3
+ export type InputsOf<CommandClass> = CommandClass extends RemoteCommand<infer Inputs, any, any> ? Inputs : never
4
+ export type ResultOf<CommandClass> = CommandClass extends RemoteCommand<any, infer Result, any> ? Result : never
5
+ export type ErrorOf<CommandClass> = CommandClass extends RemoteCommand<any, any, infer Error> ? Error : never
6
+
7
+ export interface RemoteCommandConstructor<CommandT extends RemoteCommand<any, any, any>> {
8
+ new (inputs: InputsOf<CommandT>): CommandT
9
+ fullCommandName: string
10
+ }
@@ -0,0 +1,75 @@
1
+ import type RemoteCommand from '../base/RemoteCommand'
2
+ import { type Outcome } from '../base/Outcome'
3
+ import {
4
+ type InputsOf, type ResultOf, type ErrorOf, type RemoteCommandConstructor
5
+ } from '../base/RemoteCommandTypes'
6
+ import type Query from '../base/Query'
7
+ import { useState, useRef, useEffect } from 'react'
8
+ import { getQuery } from '../base/QueryCache'
9
+
10
+ interface QueryState<CommandT extends RemoteCommand<any, any, any>> {
11
+ isLoading: boolean
12
+ isPending: boolean
13
+ isFailure: boolean
14
+ isSuccess: boolean
15
+ isError: boolean
16
+ outcome: null | Outcome<ResultOf<CommandT>, ErrorOf<CommandT>>
17
+ result: null | ResultOf<CommandT>
18
+ errors: null | Array<ErrorOf<CommandT>>
19
+ failure: any
20
+ setInputs: (inputs: InputsOf<CommandT>) => void
21
+ setDirty: () => void
22
+ }
23
+
24
+ function queryToQueryState<CommandT extends RemoteCommand<any, any, any>> (
25
+ query: Query<CommandT>
26
+ ): QueryState<CommandT> {
27
+ return {
28
+ isLoading: query.isLoading,
29
+ isPending: query.isPending,
30
+ isFailure: query.isFailure,
31
+ isSuccess: query.isSuccess,
32
+ isError: query.isError,
33
+ outcome: query.outcome,
34
+ result: query.result,
35
+ errors: query.errors,
36
+ failure: query.failure,
37
+ setInputs: (inputs: InputsOf<CommandT>) => { query.setInputs(inputs) },
38
+ setDirty: () => { query.setDirty() }
39
+ }
40
+ }
41
+
42
+ export default function useQuery<CommandT extends RemoteCommand<any, any, any>> (
43
+ CommandClass: RemoteCommandConstructor<CommandT>,
44
+ inputs: InputsOf<CommandT> | undefined = undefined
45
+ ): QueryState<CommandT> {
46
+ const queryRef = useRef<Query<CommandT>>(null)
47
+
48
+ let query = queryRef.current
49
+
50
+ if (query == null) {
51
+ if (arguments.length === 2) {
52
+ query = getQuery(CommandClass, inputs as InputsOf<CommandT>)
53
+ } else {
54
+ query = getQuery(CommandClass)
55
+ }
56
+
57
+ queryRef.current = query
58
+ }
59
+
60
+ useEffect(() => {
61
+ const unsubscribe = query?.onChange(() => {
62
+ if (query != null) { // just here to satisfy type checker
63
+ setQueryState(queryToQueryState<CommandT>(query))
64
+ }
65
+ })
66
+
67
+ return unsubscribe
68
+ }, [query])
69
+
70
+ const [queryState, setQueryState] = useState<QueryState<CommandT>>(
71
+ queryToQueryState<CommandT>(query)
72
+ )
73
+
74
+ return queryState
75
+ }
@@ -0,0 +1,5 @@
1
+ import { GetCurrentUser } from './BlogWww/GetCurrentUser'
2
+ import { getQuery } from './base/QueryCache'
3
+ import { setGetCurrentUserQuery } from './Foobara/Auth/utils/accessTokens'
4
+
5
+ setGetCurrentUserQuery(getQuery(GetCurrentUser, undefined))
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara-typescript-remote-command-generator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.20
4
+ version: 0.0.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-03 00:00:00.000000000 Z
10
+ date: 2025-05-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: foobara-files-generator
@@ -50,6 +50,7 @@ files:
50
50
  - src/remote_generator/services/auth/refresh_login_generator.rb
51
51
  - src/remote_generator/services/auth/requires_auth_command_generator.rb
52
52
  - src/remote_generator/services/auth/requires_auth_generator.rb
53
+ - src/remote_generator/services/auth/setup_generator.rb
53
54
  - src/remote_generator/services/command_errors_generator.rb
54
55
  - src/remote_generator/services/command_errors_index_generator.rb
55
56
  - src/remote_generator/services/command_generator.rb
@@ -108,7 +109,12 @@ files:
108
109
  - templates/base/Error.ts
109
110
  - templates/base/Model.ts
110
111
  - templates/base/Outcome.ts
112
+ - templates/base/Query.ts
113
+ - templates/base/QueryCache.ts
111
114
  - templates/base/RemoteCommand.ts
115
+ - templates/base/RemoteCommandTypes.ts
116
+ - templates/hooks/useQuery.ts
117
+ - templates/setup.ts.erb
112
118
  homepage: https://github.com/foobara/typescript-remote-command-generator
113
119
  licenses:
114
120
  - Apache-2.0