@blueprint-ts/core 3.0.0 → 4.0.0-beta.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 (211) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +25 -1
  3. package/docs/.vitepress/config.ts +81 -23
  4. package/docs/index.md +6 -63
  5. package/docs/{services/laravel → laravel}/pagination.md +19 -6
  6. package/docs/{services/laravel → laravel}/requests.md +2 -2
  7. package/docs/services/pagination/index.md +46 -0
  8. package/docs/services/pagination/infinite-scroller.md +20 -0
  9. package/docs/services/pagination/page-aware.md +51 -0
  10. package/docs/services/pagination/state-pagination.md +77 -0
  11. package/docs/services/pagination/updating-rows.md +52 -0
  12. package/docs/services/persistence/index.md +46 -0
  13. package/docs/services/requests/abort-requests.md +29 -0
  14. package/docs/services/requests/bulk-requests.md +70 -0
  15. package/docs/services/requests/concurrency.md +58 -0
  16. package/docs/services/requests/drivers.md +50 -0
  17. package/docs/services/requests/error-handling.md +153 -0
  18. package/docs/services/requests/events.md +31 -0
  19. package/docs/services/requests/getting-started.md +201 -0
  20. package/docs/services/requests/headers.md +40 -0
  21. package/docs/services/requests/loading.md +63 -0
  22. package/docs/services/requests/request-bodies.md +59 -0
  23. package/docs/services/requests/responses.md +34 -0
  24. package/docs/services/support/deferred-promise.md +63 -0
  25. package/docs/services/support/helpers.md +77 -0
  26. package/docs/services/support/index.md +6 -0
  27. package/docs/upgrading/v1-to-v2.md +64 -0
  28. package/docs/upgrading/v2-to-v3.md +52 -0
  29. package/docs/upgrading/v3-to-v4.md +214 -0
  30. package/docs/upgrading.md +0 -0
  31. package/docs/vue/composables/use-confirm-dialog.md +96 -0
  32. package/docs/vue/composables/use-global-checkbox.md +73 -0
  33. package/docs/vue/composables/use-is-empty.md +26 -0
  34. package/docs/vue/composables/use-is-open-from-var.md +32 -0
  35. package/docs/vue/composables/use-is-open.md +28 -0
  36. package/docs/vue/composables/use-model-wrapper.md +29 -0
  37. package/docs/vue/composables/use-on-open.md +26 -0
  38. package/docs/vue/forms/arrays.md +102 -0
  39. package/docs/vue/forms/errors.md +52 -0
  40. package/docs/vue/forms/index.md +99 -0
  41. package/docs/vue/forms/payloads.md +99 -0
  42. package/docs/vue/forms/persistence.md +19 -0
  43. package/docs/vue/forms/state-and-properties.md +26 -0
  44. package/docs/vue/forms/utilities.md +27 -0
  45. package/docs/vue/forms/validation.md +189 -0
  46. package/docs/vue/requests/loading.md +51 -0
  47. package/docs/vue/{requests → router}/route-resource-binding.md +33 -27
  48. package/docs/vue/state.md +27 -11
  49. package/package.json +12 -13
  50. package/release-tool.json +22 -3
  51. package/src/{service/bulkRequests → bulkRequests}/BulkRequestSender.ts +29 -17
  52. package/src/{service/bulkRequests → bulkRequests}/BulkRequestWrapper.ts +5 -5
  53. package/src/laravel/pagination/dataDrivers/RequestDriver.ts +30 -0
  54. package/src/laravel/pagination/index.ts +6 -0
  55. package/src/pagination/BasePaginator.ts +94 -0
  56. package/src/{service/pagination → pagination}/InfiniteScroller.ts +1 -0
  57. package/src/{service/pagination → pagination}/PageAwarePaginator.ts +34 -26
  58. package/src/{service/pagination → pagination}/StatePaginator.ts +2 -8
  59. package/src/{service/pagination → pagination}/index.ts +1 -3
  60. package/src/{service/requests → requests}/BaseRequest.ts +89 -4
  61. package/src/requests/ErrorHandler.ts +144 -0
  62. package/src/requests/RequestConcurrencyMode.enum.ts +6 -0
  63. package/src/requests/RequestErrorRouter.ts +89 -0
  64. package/src/{service/requests → requests}/bodies/FormDataBody.ts +10 -6
  65. package/src/{service/requests → requests}/contracts/BaseRequestContract.ts +3 -0
  66. package/src/requests/exceptions/BadGatewayException.ts +3 -0
  67. package/src/requests/exceptions/BadRequestException.ts +3 -0
  68. package/src/requests/exceptions/ConflictException.ts +3 -0
  69. package/src/requests/exceptions/ForbiddenException.ts +3 -0
  70. package/src/requests/exceptions/GatewayTimeoutException.ts +3 -0
  71. package/src/requests/exceptions/GoneException.ts +3 -0
  72. package/src/requests/exceptions/InvalidJsonException.ts +15 -0
  73. package/src/requests/exceptions/LockedException.ts +3 -0
  74. package/src/requests/exceptions/MethodNotAllowedException.ts +3 -0
  75. package/src/requests/exceptions/NotImplementedException.ts +3 -0
  76. package/src/requests/exceptions/PayloadTooLargeException.ts +3 -0
  77. package/src/requests/exceptions/PreconditionFailedException.ts +3 -0
  78. package/src/requests/exceptions/RequestTimeoutException.ts +3 -0
  79. package/src/requests/exceptions/ServiceUnavailableException.ts +3 -0
  80. package/src/requests/exceptions/StaleResponseException.ts +13 -0
  81. package/src/requests/exceptions/TooManyRequestsException.ts +3 -0
  82. package/src/requests/exceptions/UnsupportedMediaTypeException.ts +3 -0
  83. package/src/requests/exceptions/index.ts +51 -0
  84. package/src/requests/factories/FormDataFactory.ts +17 -0
  85. package/src/{service/requests → requests}/index.ts +9 -3
  86. package/src/requests/types/RequestConcurrencyOptions.ts +6 -0
  87. package/src/{service/support → support}/DeferredPromise.ts +1 -1
  88. package/src/support/index.ts +4 -0
  89. package/src/vue/composables/useConfirmDialog.ts +5 -1
  90. package/src/vue/composables/useModelWrapper.ts +3 -0
  91. package/src/vue/forms/BaseForm.ts +512 -399
  92. package/src/vue/forms/PropertyAwareArray.ts +6 -2
  93. package/src/vue/forms/index.ts +4 -4
  94. package/src/vue/forms/validation/index.ts +5 -2
  95. package/src/vue/forms/validation/rules/ConfirmedRule.ts +3 -3
  96. package/src/vue/forms/validation/rules/EmailRule.ts +23 -0
  97. package/src/vue/forms/validation/rules/JsonRule.ts +28 -0
  98. package/src/vue/forms/validation/types/BidirectionalRule.ts +2 -2
  99. package/src/vue/forms/validation/types/ValidationRules.ts +15 -0
  100. package/src/vue/index.ts +3 -3
  101. package/src/vue/requests/factories/VueRequestLoaderFactory.ts +3 -2
  102. package/src/vue/requests/loaders/VueRequestBatchLoader.ts +6 -1
  103. package/src/vue/requests/loaders/VueRequestLoader.ts +1 -1
  104. package/src/vue/router/routeResourceBinding/types.ts +3 -3
  105. package/src/vue/state/State.ts +38 -50
  106. package/tests/service/helpers/mergeDeep.test.ts +1 -1
  107. package/tests/service/laravel/pagination/dataDrivers/RequestDriver.test.ts +3 -3
  108. package/tests/service/laravel/requests/JsonBaseRequest.test.ts +4 -4
  109. package/tests/service/laravel/requests/PaginationJsonBaseRequest.test.ts +3 -3
  110. package/tests/service/laravel/requests/responses/JsonResponse.test.ts +2 -2
  111. package/tests/service/laravel/requests/responses/PaginationResponse.test.ts +2 -2
  112. package/tests/service/pagination/dtos/PaginationDataDto.test.ts +1 -1
  113. package/tests/service/pagination/factories/VuePaginationDriverFactory.test.ts +2 -2
  114. package/tests/service/pagination/frontendDrivers/VuePaginationDriver.test.ts +1 -1
  115. package/tests/service/requests/ErrorHandler.test.ts +61 -58
  116. package/tests/service/requests/FormDataBody.test.ts +1 -1
  117. package/tests/vue/forms/BaseForm.behavior.test.ts +98 -0
  118. package/docs/.vitepress/theme/Layout.vue +0 -14
  119. package/docs/.vitepress/theme/components/VersionSelector.vue +0 -64
  120. package/docs/.vitepress/theme/index.js +0 -13
  121. package/docs/services/requests/index.md +0 -74
  122. package/docs/vue/forms.md +0 -477
  123. package/examples/files/7z2404-x64.exe +0 -0
  124. package/examples/index.html +0 -14
  125. package/examples/js/app.js +0 -8
  126. package/examples/js/router.js +0 -22
  127. package/examples/js/view/App.vue +0 -49
  128. package/examples/js/view/layout/DemoPage.vue +0 -28
  129. package/examples/js/view/pagination/Pagination.vue +0 -28
  130. package/examples/js/view/pagination/components/errorPagination/ErrorPagination.vue +0 -71
  131. package/examples/js/view/pagination/components/errorPagination/GetProductsRequest.ts +0 -54
  132. package/examples/js/view/pagination/components/infiniteScrolling/GetProductsRequest.ts +0 -50
  133. package/examples/js/view/pagination/components/infiniteScrolling/InfiniteScrolling.vue +0 -57
  134. package/examples/js/view/pagination/components/tablePagination/GetProductsRequest.ts +0 -50
  135. package/examples/js/view/pagination/components/tablePagination/TablePagination.vue +0 -63
  136. package/examples/js/view/requests/Requests.vue +0 -34
  137. package/examples/js/view/requests/components/abortableRequest/AbortableRequest.vue +0 -36
  138. package/examples/js/view/requests/components/abortableRequest/GetProductsRequest.ts +0 -25
  139. package/examples/js/view/requests/components/fileDownloadRequest/DownloadFileRequest.ts +0 -15
  140. package/examples/js/view/requests/components/fileDownloadRequest/FileDownloadRequest.vue +0 -44
  141. package/examples/js/view/requests/components/getRequestWithDynamicParams/GetProductsRequest.ts +0 -34
  142. package/examples/js/view/requests/components/getRequestWithDynamicParams/GetRequestWithDynamicParams.vue +0 -59
  143. package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.ts +0 -21
  144. package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.vue +0 -53
  145. package/src/service/laravel/pagination/contracts/PaginationParamsContract.ts +0 -4
  146. package/src/service/laravel/pagination/dataDrivers/RequestDriver.ts +0 -32
  147. package/src/service/laravel/pagination/index.ts +0 -7
  148. package/src/service/pagination/BasePaginator.ts +0 -36
  149. package/src/service/pagination/Paginator.ts +0 -11
  150. package/src/service/requests/ErrorHandler.ts +0 -64
  151. package/src/service/requests/exceptions/index.ts +0 -19
  152. package/src/service/requests/factories/FormDataFactory.ts +0 -9
  153. package/src/service/support/index.ts +0 -3
  154. /package/src/{service/bulkRequests → bulkRequests}/BulkRequestEvent.enum.ts +0 -0
  155. /package/src/{service/bulkRequests → bulkRequests}/index.ts +0 -0
  156. /package/src/{service/laravel → laravel}/pagination/contracts/PaginationResponseBodyContract.ts +0 -0
  157. /package/src/{service/laravel → laravel}/requests/JsonBaseRequest.ts +0 -0
  158. /package/src/{service/laravel → laravel}/requests/PaginationJsonBaseRequest.ts +0 -0
  159. /package/src/{service/laravel → laravel}/requests/index.ts +0 -0
  160. /package/src/{service/laravel → laravel}/requests/responses/JsonResponse.ts +0 -0
  161. /package/src/{service/laravel → laravel}/requests/responses/PaginationResponse.ts +0 -0
  162. /package/src/{service/pagination → pagination}/contracts/BaseViewDriverContract.ts +0 -0
  163. /package/src/{service/pagination → pagination}/contracts/BaseViewDriverFactoryContract.ts +0 -0
  164. /package/src/{service/pagination → pagination}/contracts/PaginateableRequestContract.ts +0 -0
  165. /package/src/{service/pagination → pagination}/contracts/PaginationDataDriverContract.ts +0 -0
  166. /package/src/{service/pagination → pagination}/contracts/PaginationResponseContract.ts +0 -0
  167. /package/src/{service/pagination → pagination}/contracts/PaginatorLoadDataOptions.ts +0 -0
  168. /package/src/{service/pagination → pagination}/contracts/StatePaginationDataDriverContract.ts +0 -0
  169. /package/src/{service/pagination → pagination}/contracts/ViewDriverContract.ts +0 -0
  170. /package/src/{service/pagination → pagination}/contracts/ViewDriverFactoryContract.ts +0 -0
  171. /package/src/{service/pagination → pagination}/dataDrivers/ArrayDriver.ts +0 -0
  172. /package/src/{service/pagination → pagination}/dtos/PaginationDataDto.ts +0 -0
  173. /package/src/{service/pagination → pagination}/dtos/StatePaginationDataDto.ts +0 -0
  174. /package/src/{service/pagination → pagination}/factories/VueBaseViewDriverFactory.ts +0 -0
  175. /package/src/{service/pagination → pagination}/factories/VuePaginationDriverFactory.ts +0 -0
  176. /package/src/{service/pagination → pagination}/frontendDrivers/VueBaseViewDriver.ts +0 -0
  177. /package/src/{service/pagination → pagination}/frontendDrivers/VuePaginationDriver.ts +0 -0
  178. /package/src/{service/persistenceDrivers → persistenceDrivers}/LocalStorageDriver.ts +0 -0
  179. /package/src/{service/persistenceDrivers → persistenceDrivers}/NonPersistentDriver.ts +0 -0
  180. /package/src/{service/persistenceDrivers → persistenceDrivers}/SessionStorageDriver.ts +0 -0
  181. /package/src/{service/persistenceDrivers → persistenceDrivers}/index.ts +0 -0
  182. /package/src/{service/persistenceDrivers → persistenceDrivers}/types/PersistenceDriver.ts +0 -0
  183. /package/src/{service/requests → requests}/RequestEvents.enum.ts +0 -0
  184. /package/src/{service/requests → requests}/RequestMethod.enum.ts +0 -0
  185. /package/src/{service/requests → requests}/bodies/JsonBody.ts +0 -0
  186. /package/src/{service/requests → requests}/contracts/AbortableRequestContract.ts +0 -0
  187. /package/src/{service/requests → requests}/contracts/BodyContract.ts +0 -0
  188. /package/src/{service/requests → requests}/contracts/BodyFactoryContract.ts +0 -0
  189. /package/src/{service/requests → requests}/contracts/DriverConfigContract.ts +0 -0
  190. /package/src/{service/requests → requests}/contracts/HeadersContract.ts +0 -0
  191. /package/src/{service/requests → requests}/contracts/RequestDriverContract.ts +0 -0
  192. /package/src/{service/requests → requests}/contracts/RequestLoaderContract.ts +0 -0
  193. /package/src/{service/requests → requests}/contracts/RequestLoaderFactoryContract.ts +0 -0
  194. /package/src/{service/requests → requests}/contracts/ResponseContract.ts +0 -0
  195. /package/src/{service/requests → requests}/drivers/contracts/ResponseHandlerContract.ts +0 -0
  196. /package/src/{service/requests → requests}/drivers/fetch/FetchDriver.ts +0 -0
  197. /package/src/{service/requests → requests}/drivers/fetch/FetchResponse.ts +0 -0
  198. /package/src/{service/requests → requests}/exceptions/NoResponseReceivedException.ts +0 -0
  199. /package/src/{service/requests → requests}/exceptions/NotFoundException.ts +0 -0
  200. /package/src/{service/requests → requests}/exceptions/PageExpiredException.ts +0 -0
  201. /package/src/{service/requests → requests}/exceptions/ResponseBodyException.ts +0 -0
  202. /package/src/{service/requests → requests}/exceptions/ResponseException.ts +0 -0
  203. /package/src/{service/requests → requests}/exceptions/ServerErrorException.ts +0 -0
  204. /package/src/{service/requests → requests}/exceptions/UnauthorizedException.ts +0 -0
  205. /package/src/{service/requests → requests}/exceptions/ValidationException.ts +0 -0
  206. /package/src/{service/requests → requests}/factories/JsonBodyFactory.ts +0 -0
  207. /package/src/{service/requests → requests}/responses/BaseResponse.ts +0 -0
  208. /package/src/{service/requests → requests}/responses/BlobResponse.ts +0 -0
  209. /package/src/{service/requests → requests}/responses/JsonResponse.ts +0 -0
  210. /package/src/{service/requests → requests}/responses/PlainTextResponse.ts +0 -0
  211. /package/src/{helpers.ts → support/helpers.ts} +0 -0
@@ -0,0 +1,26 @@
1
+ # State And Properties
2
+
3
+ `BaseForm` tracks the original state and the current state of each field.
4
+
5
+ ```ts
6
+ form.isDirty() // any field modified
7
+ form.isDirty('email') // specific field modified
8
+
9
+ form.touch('email')
10
+ form.isTouched('email')
11
+ ```
12
+
13
+ **Properties**
14
+
15
+ `properties.<field>` exposes:
16
+ - `model` (a `ComputedRef` compatible with `v-model`)
17
+ - `errors` (array)
18
+ - `dirty` and `touched`
19
+
20
+ Example:
21
+
22
+ ```vue
23
+ <input v-model="form.properties.email.model.value" />
24
+ <div v-if="form.properties.email.dirty">This field has been changed</div>
25
+ <div v-if="form.properties.email.errors.length">{{ form.properties.email.errors[0] }}</div>
26
+ ```
@@ -0,0 +1,27 @@
1
+ # Utilities
2
+
3
+ Update multiple fields at once:
4
+
5
+ ```ts
6
+ form.fillState({ name: 'John Doe', email: 'john@example.com' })
7
+ ```
8
+
9
+ Synchronize a value without marking dirty:
10
+
11
+ ```ts
12
+ form.syncValue('email', 'new@example.com')
13
+ ```
14
+
15
+ Reset the form to its original state:
16
+
17
+ ```ts
18
+ form.reset()
19
+ ```
20
+
21
+ Convert `properties` back to raw data:
22
+
23
+ ```ts
24
+ import { propertyAwareToRaw } from '@blueprint-ts/core/vue/forms'
25
+
26
+ const raw = propertyAwareToRaw<FormState>(form.properties)
27
+ ```
@@ -0,0 +1,189 @@
1
+ # Validation
2
+
3
+ Define validation rules by overriding `defineRules()` in your form class.
4
+
5
+ Validation errors are exposed on `properties.<field>.errors`, as documented in [Errors](/vue/forms/errors).
6
+
7
+ ## Available Rules
8
+
9
+ - `RequiredRule` — checks that the value is not `null`, `undefined`, or `''`
10
+ - `MinRule` — minimum length for strings/arrays or minimum value for numbers
11
+ - `ConfirmedRule` — validates that two fields match (e.g. password confirmation)
12
+ - `UrlRule` — validates that the value is a valid URL
13
+ - `EmailRule` — validates that the value is a valid email address
14
+ - `JsonRule` — validates that the value is valid JSON
15
+
16
+ All rules are exported from `@blueprint-ts/core/vue/forms/validation`.
17
+
18
+ ## Validation Modes
19
+
20
+ Validation modes are bit flags. You can use the presets below or combine flags with `|`.
21
+
22
+ **Flags**
23
+
24
+ - `ValidationMode.NEVER` — never validate this field
25
+ - `ValidationMode.INSTANTLY` — validate immediately when the field is evaluated, even if not dirty/touched
26
+ - `ValidationMode.ON_TOUCH` — validate after the field is touched
27
+ - `ValidationMode.ON_DIRTY` — validate when the field becomes dirty
28
+ - `ValidationMode.ON_SUBMIT` — validate on submit
29
+ - `ValidationMode.ON_DEPENDENT_CHANGE` — validate when a dependent field changes
30
+
31
+ **Presets**
32
+
33
+ - `ValidationMode.DEFAULT` — `ON_TOUCH | ON_DIRTY | ON_SUBMIT`
34
+ - `ValidationMode.AGGRESSIVE` — `INSTANTLY | ON_TOUCH | ON_DIRTY | ON_SUBMIT`
35
+ - `ValidationMode.PASSIVE` — `ON_SUBMIT`
36
+
37
+ **Custom Combination**
38
+
39
+ ```ts
40
+ options: { mode: ValidationMode.ON_TOUCH | ValidationMode.ON_SUBMIT }
41
+ ```
42
+
43
+ **Internal vs External**
44
+
45
+ - Internal means validation runs from within `BaseForm` based on user interaction or dependencies.
46
+ - External means you explicitly trigger validation via `validate(true)`, typically on submit.
47
+
48
+ - Internal triggers: `INSTANTLY`, `ON_TOUCH`, `ON_DIRTY`, `ON_DEPENDENT_CHANGE`
49
+ - External trigger (`validate(true)`): `ON_SUBMIT` (and `PASSIVE` preset)
50
+ - `NEVER` disables validation entirely
51
+
52
+ Rules can declare dependencies via `rule.dependsOn = ['otherField']`. Some rules, such as `ConfirmedRule`, automatically set up bidirectional dependencies.
53
+
54
+ ## Externally Triggering Validation
55
+
56
+ ```ts
57
+ const ok = form.validate(true)
58
+ if (!ok) return
59
+ ```
60
+
61
+ Some modes (notably `PASSIVE` / `ON_SUBMIT`) only validate when you trigger validation manually.
62
+
63
+ ### How `validate(isSubmitting)` Behaves
64
+
65
+ - `validate(true)` enables `ON_SUBMIT` rules. Fields with `PASSIVE` mode will validate only here.
66
+ - `validate(false)` still validates fields that are currently dirty, touched, or set to `INSTANTLY`.
67
+ - `ValidationMode.NEVER` prevents validation in all cases, even during submit.
68
+
69
+ ## Typing `defineRules`
70
+
71
+ Use `ValidationRules<FormBody>` for a concise, strongly-typed return type:
72
+
73
+ ```ts
74
+ import { type ValidationRules } from '@blueprint-ts/core/vue/forms/validation'
75
+
76
+ protected override defineRules(): ValidationRules<MyFormBody> {
77
+ return {
78
+ // ...
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## Custom Rules
84
+
85
+ Create your own rules by extending `BaseRule`:
86
+
87
+ ```ts
88
+ import { BaseRule } from '@blueprint-ts/core/vue/forms/validation'
89
+
90
+ export class MaxRule<FormBody extends object> extends BaseRule<FormBody> {
91
+ public constructor(
92
+ protected max: number,
93
+ protected message: string = 'Value is too large'
94
+ ) {
95
+ super()
96
+ }
97
+
98
+ public validate(value: unknown): boolean {
99
+ if (value === null || value === undefined) {
100
+ return true
101
+ }
102
+
103
+ if (typeof value === 'string') {
104
+ return value.length <= this.max
105
+ }
106
+
107
+ if (typeof value === 'number') {
108
+ return value <= this.max
109
+ }
110
+
111
+ if (Array.isArray(value)) {
112
+ return value.length <= this.max
113
+ }
114
+
115
+ return false
116
+ }
117
+
118
+ public getMessage(): string {
119
+ return this.message
120
+ }
121
+ }
122
+ ```
123
+
124
+ If a rule depends on other fields, set `dependsOn` or implement bidirectional validation:
125
+
126
+ ```ts
127
+ import { BaseRule } from '@blueprint-ts/core/vue/forms/validation'
128
+ import { type BidirectionalRule } from '@blueprint-ts/core/vue/forms/validation'
129
+
130
+ export class MatchesOtherRule<FormBody extends object> extends BaseRule<FormBody> implements BidirectionalRule {
131
+ public dependsOn: Array<keyof FormBody> = ['other']
132
+
133
+ public validate(value: unknown, state: FormBody): boolean {
134
+ return value === state['other' as keyof FormBody]
135
+ }
136
+
137
+ public getMessage(): string {
138
+ return 'Values do not match'
139
+ }
140
+
141
+ public getBidirectionalFields(): string[] {
142
+ return ['other']
143
+ }
144
+ }
145
+ ```
146
+
147
+ ## Example
148
+
149
+ ```ts
150
+ import { BaseForm } from '@blueprint-ts/core/vue/forms'
151
+ import { ConfirmedRule, MinRule, RequiredRule, type ValidationRules } from '@blueprint-ts/core/vue/forms/validation'
152
+
153
+ export interface PinUpdateFormBody {
154
+ current_pin: string
155
+ new_pin: string
156
+ new_pin_confirmation: string
157
+ }
158
+
159
+ export class PinUpdateForm extends BaseForm<PinUpdateFormBody, PinUpdateFormBody> {
160
+ public constructor() {
161
+ super({
162
+ current_pin: '',
163
+ new_pin: '',
164
+ new_pin_confirmation: ''
165
+ })
166
+ }
167
+
168
+ protected override defineRules(): ValidationRules<PinUpdateFormBody> {
169
+ return {
170
+ current_pin: {
171
+ rules: [
172
+ new RequiredRule('This field is required.'),
173
+ new MinRule(4, 'This field must be at least 4 characters long.')
174
+ ]
175
+ },
176
+ new_pin: {
177
+ rules: [
178
+ new RequiredRule('This field is required.'),
179
+ new MinRule(4, 'This field must be at least 4 characters long.'),
180
+ new ConfirmedRule('new_pin_confirmation', 'This field must match the confirmation.')
181
+ ]
182
+ },
183
+ new_pin_confirmation: {
184
+ rules: [new RequiredRule('This field is required.')]
185
+ }
186
+ }
187
+ }
188
+ }
189
+ ```
@@ -0,0 +1,51 @@
1
+ # Loading
2
+
3
+ For Vue apps, the library includes `VueRequestLoader`, `VueRequestLoaderFactory`, and `VueRequestBatchLoader`, which
4
+ use Vue refs to track loading state.
5
+
6
+ ## Registering the Vue Loader Factory
7
+
8
+ ```typescript
9
+ import { BaseRequest } from '@blueprint-ts/core/requests'
10
+ import { VueRequestLoaderFactory } from '@blueprint-ts/core/vue/requests'
11
+
12
+ BaseRequest.setRequestLoaderFactory(new VueRequestLoaderFactory())
13
+ ```
14
+
15
+ ## Reading Loading State
16
+
17
+ ```typescript
18
+ const request = new ExpenseIndexRequest()
19
+
20
+ request.send()
21
+
22
+ const isLoading = request.isLoading()
23
+ // isLoading is a Ref<boolean> when using VueRequestLoaderFactory
24
+ ```
25
+
26
+ ## Batch Loading
27
+
28
+ `VueRequestBatchLoader` can track the loading state of multiple requests. Use it when you want a single loading ref to
29
+ reflect a batch of requests.
30
+
31
+ ```typescript
32
+ import { VueRequestBatchLoader } from '@blueprint-ts/core/vue/requests'
33
+
34
+ const batchLoader = new VueRequestBatchLoader(2)
35
+ batchLoader.startBatch(2)
36
+
37
+ const requestA = new ExpenseIndexRequest().setRequestLoader(batchLoader)
38
+ const requestB = new ExpenseIndexRequest().setRequestLoader(batchLoader)
39
+
40
+ requestA.send()
41
+ requestB.send()
42
+
43
+ const isLoading = batchLoader.isLoading()
44
+ ```
45
+
46
+ If the batch will not complete (for example, when a chained request fails), call `abortBatch()` to stop waiting for the
47
+ remaining requests:
48
+
49
+ ```typescript
50
+ batchLoader.abortBatch()
51
+ ```
@@ -7,7 +7,7 @@ When using `vue-router`, you can automatically bind route parameters to resource
7
7
  To enable the router to load resources automatically, install the route injection plugin when initializing your router:
8
8
 
9
9
  ```ts
10
- import { installRouteInjection } from '@blueprint-ts/core'
10
+ import { installRouteInjection } from '@blueprint-ts/core/vue/router/routeResourceBinding'
11
11
 
12
12
  installRouteInjection(router)
13
13
  ```
@@ -17,7 +17,7 @@ installRouteInjection(router)
17
17
  Use the `defineRoute` helper to define your routes and specify which parameters should be resolved into resources:
18
18
 
19
19
  ```ts
20
- import { defineRoute, RouteResourceRequestResolver } from '@blueprint-ts/core'
20
+ import { defineRoute, RouteResourceRequestResolver } from '@blueprint-ts/core/vue/router/routeResourceBinding'
21
21
  import ProductDetailPage from '@/pages/ProductDetailPage.vue'
22
22
 
23
23
  export default defineRoute<{
@@ -26,20 +26,22 @@ export default defineRoute<{
26
26
  path: ':productId',
27
27
  name: 'products.show',
28
28
  component: ProductDetailPage,
29
- inject: {
30
- product: {
31
- from: 'productId',
32
- resolve: (productId: string) => {
33
- return new RouteResourceRequestResolver(
34
- new ProductShowRequest(productId)
35
- )
29
+ meta: {
30
+ inject: {
31
+ product: {
32
+ from: 'productId',
33
+ resolve: (productId: string) => {
34
+ return new RouteResourceRequestResolver(
35
+ new ProductShowRequest(productId)
36
+ )
37
+ }
36
38
  }
37
39
  }
38
40
  }
39
41
  })
40
42
  ```
41
43
 
42
- Navigation is **non-blocking** the route navigates immediately while resources resolve in the background. Cached values are reused when navigating between child routes with unchanged parameters.
44
+ Navigation is blocking by default. Cached values are reused when navigating between child routes with unchanged parameters.
43
45
 
44
46
  ## Usage in Components
45
47
 
@@ -60,7 +62,7 @@ const props = defineProps<{
60
62
  `RouteResourceBoundView` is a drop-in replacement for `<RouterView>` that automatically handles loading and error states. Define error and loading components directly in the route:
61
63
 
62
64
  ```ts
63
- import { defineRoute, RouteResourceRequestResolver } from '@blueprint-ts/core'
65
+ import { defineRoute, RouteResourceRequestResolver } from '@blueprint-ts/core/vue/router/routeResourceBinding'
64
66
  import ProductDetailPage from '@/pages/ProductDetailPage.vue'
65
67
  import GenericErrorPage from '@/pages/GenericErrorPage.vue'
66
68
  import LoadingSpinner from '@/components/LoadingSpinner.vue'
@@ -73,13 +75,15 @@ export default defineRoute<{
73
75
  component: ProductDetailPage,
74
76
  errorComponent: GenericErrorPage,
75
77
  loadingComponent: LoadingSpinner,
76
- inject: {
77
- product: {
78
- from: 'productId',
79
- resolve: (productId: string) => {
80
- return new RouteResourceRequestResolver(
81
- new ProductShowRequest(productId)
82
- )
78
+ meta: {
79
+ inject: {
80
+ product: {
81
+ from: 'productId',
82
+ resolve: (productId: string) => {
83
+ return new RouteResourceRequestResolver(
84
+ new ProductShowRequest(productId)
85
+ )
86
+ }
83
87
  }
84
88
  }
85
89
  }
@@ -94,7 +98,7 @@ Then replace `<RouterView>` with `<RouteResourceBoundView>` in your layout:
94
98
  </template>
95
99
 
96
100
  <script setup lang="ts">
97
- import { RouteResourceBoundView } from '@blueprint-ts/core'
101
+ import { RouteResourceBoundView } from '@blueprint-ts/core/vue/router/routeResourceBinding'
98
102
  </script>
99
103
  ```
100
104
 
@@ -164,13 +168,15 @@ export default defineRoute<{
164
168
  name: 'products.show',
165
169
  component: ProductDetailPage,
166
170
  lazy: false,
167
- inject: {
168
- product: {
169
- from: 'productId',
170
- resolve: (productId: string) => {
171
- return new RouteResourceRequestResolver(
172
- new ProductShowRequest(productId)
173
- )
171
+ meta: {
172
+ inject: {
173
+ product: {
174
+ from: 'productId',
175
+ resolve: (productId: string) => {
176
+ return new RouteResourceRequestResolver(
177
+ new ProductShowRequest(productId)
178
+ )
179
+ }
174
180
  }
175
181
  }
176
182
  }
@@ -192,7 +198,7 @@ Then use the `useRouteResource` composable inside your component:
192
198
  </template>
193
199
 
194
200
  <script setup lang="ts">
195
- import { useRouteResource } from '@blueprint-ts/core'
201
+ import { useRouteResource } from '@blueprint-ts/core/vue/router/routeResourceBinding'
196
202
 
197
203
  const props = defineProps<{
198
204
  product: ProductResource
package/docs/vue/state.md CHANGED
@@ -19,6 +19,9 @@ The `State` class provides a reactive, type-safe state management system with ri
19
19
  Create a new state by extending the base class and defining your interface:
20
20
 
21
21
  ```typescript
22
+ import { State } from '@blueprint-ts/core/vue/state'
23
+ import { type PersistenceDriver, LocalStorageDriver } from '@blueprint-ts/core/persistenceDrivers'
24
+
22
25
  // Define your state interface
23
26
  interface UserStateInterface {
24
27
  name: string;
@@ -42,14 +45,15 @@ class UserState extends State<UserStateInterface> {
42
45
  }
43
46
  },
44
47
  {
45
- persist: true // Enable persistence
48
+ persist: true, // Enable persistence
49
+ persistSuffix: 'user' // Optional namespace for the storage key
46
50
  }
47
51
  );
48
52
  }
49
53
 
50
54
  // Optional: Override persistence method
51
55
  protected override getPersistenceDriver(): PersistenceDriver {
52
- return new LocalStorageDriver();
56
+ return new LocalStorageDriver()
53
57
  }
54
58
  }
55
59
 
@@ -81,6 +85,16 @@ userState.subscribe(
81
85
  );
82
86
  ````
83
87
 
88
+ `subscribe` returns an unsubscribe function:
89
+
90
+ ````typescript
91
+ const unsubscribe = userState.subscribe('name', () => {
92
+ // ...
93
+ });
94
+
95
+ unsubscribe();
96
+ ````
97
+
84
98
  #### Subscribing to nested properties
85
99
 
86
100
  ````typescript
@@ -121,7 +135,7 @@ userState.subscribe(
121
135
  ````
122
136
 
123
137
  ### State Persistence
124
- Enable state persistence by passing `persist: true` in the constructor options. Override the method `getPersistenceDriver` to use different storage mechanisms:
138
+ Enable state persistence by passing `persist: true` in the constructor options. You can optionally set `persistSuffix` to namespace the storage key. Override the method `getPersistenceDriver` to use different storage mechanisms:
125
139
 
126
140
  ````typescript
127
141
  // Examples of different persistence drivers:
@@ -137,6 +151,8 @@ protected override getPersistenceDriver(): PersistenceDriver {
137
151
  }
138
152
  ````
139
153
 
154
+ You can inspect the computed key via `state.persistKey`.
155
+
140
156
  ### Importing and Exporting State
141
157
 
142
158
  Export the current state to a plain object or import values:
@@ -154,13 +170,6 @@ userState.import({
154
170
  }
155
171
  });
156
172
 
157
- // Import with hook suppression (doesn't trigger change handlers)
158
- userState.import(
159
- {
160
- name: 'Jane Doe'
161
- },
162
- true // suppress hooks
163
- );
164
173
  ````
165
174
 
166
175
  ### Resetting State
@@ -181,6 +190,13 @@ userState.subscribe(
181
190
  );
182
191
  ````
183
192
 
193
+ ### Cleanup
194
+ If you create short‑lived state instances, call `destroy()` to remove watchers and release references:
195
+
196
+ ````typescript
197
+ userState.destroy();
198
+ ````
199
+
184
200
  ## Advanced Features
185
201
  ### Deep Object Watching
186
202
  When you modify nested properties, the system automatically detects these changes and triggers appropriate callbacks:
@@ -290,4 +306,4 @@ formState.state.password = 'securePassword123';
290
306
  document.getElementById('reset').addEventListener('click', () => {
291
307
  formState.reset();
292
308
  });
293
- ````
309
+ ````
package/package.json CHANGED
@@ -1,21 +1,20 @@
1
1
  {
2
2
  "name": "@blueprint-ts/core",
3
- "version": "3.0.0",
3
+ "version": "4.0.0-beta.10",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "type": "module",
9
9
  "exports": {
10
- "./service/pagination": "./src/service/pagination/index.ts",
11
- "./service/support": "./src/service/support/index.ts",
12
- "./service/requests": "./src/service/requests/index.ts",
13
- "./service/bulkRequests": "./src/service/bulkRequests/index.ts",
14
- "./service/persistenceDrivers": "./src/service/persistenceDrivers/index.ts",
15
- "./service/requests/exceptions": "./src/service/requests/exceptions/index.ts",
16
- "./service/laravel/requests": "./src/service/laravel/requests/index.ts",
17
- "./service/laravel/pagination": "./src/service/laravel/pagination/index.ts",
18
- "./helpers": "./src/helpers.ts",
10
+ "./pagination": "./src/pagination/index.ts",
11
+ "./support": "./src/support/index.ts",
12
+ "./requests": "./src/requests/index.ts",
13
+ "./bulkRequests": "./src/bulkRequests/index.ts",
14
+ "./persistenceDrivers": "./src/persistenceDrivers/index.ts",
15
+ "./requests/exceptions": "./src/requests/exceptions/index.ts",
16
+ "./laravel/requests": "./src/laravel/requests/index.ts",
17
+ "./laravel/pagination": "./src/laravel/pagination/index.ts",
19
18
  "./vue": "./src/vue/index.ts",
20
19
  "./vue/forms": "./src/vue/forms/index.ts",
21
20
  "./vue/forms/validation": "./src/vue/forms/validation/index.ts",
@@ -75,8 +74,8 @@
75
74
  "vue-router": "^4.3.2"
76
75
  },
77
76
  "dependencies": {
78
- "lodash-es": "^4.17.21",
79
- "qs": "^6.12.0",
80
- "uuid": "^11.1.0"
77
+ "lodash-es": "^4.17.23",
78
+ "qs": "^6.15.0",
79
+ "uuid": "^13.0.0"
81
80
  }
82
81
  }
package/release-tool.json CHANGED
@@ -1,7 +1,26 @@
1
1
  {
2
2
  "version_file": "package.json",
3
- "version_pattern": "\"version\":\\s*\"([0-9]+\\.[0-9]+\\.[0-9]+)\"",
3
+ "version_pattern": "\"version\":\\s*\"([0-9]+\\.[0-9]+\\.[0-9]+(?:-[a-z]+\\.[0-9]+)?)\"",
4
4
  "version_template": "\"version\": \"{}\"",
5
5
  "develop_branch": "develop",
6
- "main_branch": "main"
7
- }
6
+ "main_branch": "main",
7
+ "docs": {
8
+ "build": {
9
+ "cwd": "./docs",
10
+ "commands": [
11
+ ["npm", "ci"],
12
+ ["npm", "run", "docs:build"]
13
+ ],
14
+ "output_dir": ".vitepress/dist"
15
+ },
16
+ "publish": {
17
+ "repo_path": "./docs",
18
+ "remote": "origin",
19
+ "branch": "gh-pages",
20
+ "subdir": "",
21
+ "commit_message": "docs: publish {tag}",
22
+ "add_nojekyll": true,
23
+ "force_push": false
24
+ }
25
+ }
26
+ }
@@ -1,26 +1,30 @@
1
1
  import { BulkRequestWrapper } from './BulkRequestWrapper'
2
2
  import { BulkRequestEventEnum } from './BulkRequestEvent.enum'
3
- import { BaseRequest } from '../requests'
4
3
 
5
4
  export enum BulkRequestExecutionMode {
6
5
  PARALLEL = 'parallel',
7
6
  SEQUENTIAL = 'sequential'
8
7
  }
9
8
 
10
- export class BulkRequestSender {
11
- // @ts-expect-error
12
- protected events: Map<BulkRequestEventEnum, ((req: BulkRequestWrapper<BaseRequest>) => void)[]> = new Map()
9
+ export class BulkRequestSender<
10
+ RequestLoaderLoadingType = unknown,
11
+ RequestBodyInterface = unknown,
12
+ ResponseClass = unknown,
13
+ RequestParamsInterface extends object = object
14
+ > {
15
+ protected events: Map<
16
+ BulkRequestEventEnum,
17
+ ((req: BulkRequestWrapper<RequestLoaderLoadingType, RequestBodyInterface, ResponseClass, RequestParamsInterface>) => void)[]
18
+ > = new Map()
13
19
  protected abortController: AbortController | undefined = undefined
14
20
 
15
21
  public constructor(
16
- // @ts-expect-error
17
- protected requests: BulkRequestWrapper<BaseRequest>[] = [],
22
+ protected requests: BulkRequestWrapper<RequestLoaderLoadingType, RequestBodyInterface, ResponseClass, RequestParamsInterface>[] = [],
18
23
  protected executionMode: BulkRequestExecutionMode = BulkRequestExecutionMode.PARALLEL,
19
24
  protected retryCount: number = 0
20
25
  ) {}
21
26
 
22
- // @ts-expect-error
23
- public setRequests(requests: BulkRequestWrapper<BaseRequest>[] = []) {
27
+ public setRequests(requests: BulkRequestWrapper<RequestLoaderLoadingType, RequestBodyInterface, ResponseClass, RequestParamsInterface>[] = []) {
24
28
  this.requests = requests
25
29
 
26
30
  return this
@@ -39,11 +43,13 @@ export class BulkRequestSender {
39
43
  }
40
44
 
41
45
  public get isLoading(): boolean {
42
- return this.requests.some((req) => req.isLoading())
46
+ return this.requests.some((req) => Boolean(req.isLoading() as unknown))
43
47
  }
44
48
 
45
- // @ts-expect-error
46
- public on(event: BulkRequestEventEnum, callback: (req: BulkRequestWrapper<BaseRequest>) => void): this {
49
+ public on(
50
+ event: BulkRequestEventEnum,
51
+ callback: (req: BulkRequestWrapper<RequestLoaderLoadingType, RequestBodyInterface, ResponseClass, RequestParamsInterface>) => void
52
+ ): this {
47
53
  if (!this.events.has(event)) {
48
54
  this.events.set(event, [])
49
55
  }
@@ -59,8 +65,10 @@ export class BulkRequestSender {
59
65
  return this
60
66
  }
61
67
 
62
- // @ts-expect-error
63
- protected emit(event: BulkRequestEventEnum, req: BulkRequestWrapper<BaseRequest>): void {
68
+ protected emit(
69
+ event: BulkRequestEventEnum,
70
+ req: BulkRequestWrapper<RequestLoaderLoadingType, RequestBodyInterface, ResponseClass, RequestParamsInterface>
71
+ ): void {
64
72
  const callbacks = this.events.get(event) || []
65
73
 
66
74
  callbacks.forEach((callback) => callback(req))
@@ -89,10 +97,14 @@ export class BulkRequestSender {
89
97
  }
90
98
 
91
99
  return {
92
- getSuccessCount: () => this.requests.filter((r) => !r.getError()).length,
93
- getErrorCount: () => this.requests.filter((r) => r.getError()).length,
94
- getSuccessfulResponses: () => this.requests.filter((r) => !r.getError()).map((r) => r.getResponse()),
95
- getFailedResponses: () => this.requests.filter((r) => r.getError()).map((r) => r.getError())
100
+ getSuccessCount: () => this.requests.filter((r) => !r.hasError()).length,
101
+ getErrorCount: () => this.requests.filter((r) => r.hasError()).length,
102
+ getSuccessfulResponses: () =>
103
+ this.requests
104
+ .filter((r) => !r.hasError())
105
+ .map((r) => r.getResponse())
106
+ .filter((response): response is ResponseClass => response !== null),
107
+ getFailedResponses: () => this.requests.filter((r) => r.hasError()).map((r) => r.getError())
96
108
  }
97
109
  }
98
110