@wp-typia/project-tools 0.13.4 → 0.15.0

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 (31) hide show
  1. package/dist/runtime/scaffold-onboarding.js +8 -4
  2. package/dist/runtime/scaffold.d.ts +1 -0
  3. package/dist/runtime/scaffold.js +3 -0
  4. package/dist/runtime/schema-core.d.ts +1 -0
  5. package/dist/runtime/schema-core.js +188 -8
  6. package/package.json +1 -1
  7. package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +16 -0
  8. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api-types.ts.mustache +10 -0
  9. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api.ts.mustache +51 -38
  10. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/data.ts.mustache +76 -33
  11. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/interactivity.ts.mustache +206 -41
  12. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/render.php.mustache +37 -43
  13. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/transport.ts.mustache +254 -0
  14. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/types.ts.mustache +13 -8
  15. package/templates/_shared/compound/persistence-auth/{{slugKebabCase}}.php.mustache +139 -0
  16. package/templates/_shared/compound/persistence-public/{{slugKebabCase}}.php.mustache +159 -0
  17. package/templates/_shared/persistence/auth/{{slugKebabCase}}.php.mustache +139 -0
  18. package/templates/_shared/persistence/core/scripts/sync-rest-contracts.ts.mustache +16 -0
  19. package/templates/_shared/persistence/core/src/api-types.ts.mustache +10 -0
  20. package/templates/_shared/persistence/core/src/api.ts.mustache +51 -38
  21. package/templates/_shared/persistence/core/src/data.ts.mustache +76 -33
  22. package/templates/_shared/persistence/core/src/interactivity.ts.mustache +206 -43
  23. package/templates/_shared/persistence/core/src/transport.ts.mustache +254 -0
  24. package/templates/_shared/persistence/public/{{slugKebabCase}}.php.mustache +159 -0
  25. package/templates/_shared/rest-helpers/public/inc/rest-public.php.mustache +1 -1
  26. package/templates/_shared/workspace/persistence-auth/server.php.mustache +139 -0
  27. package/templates/_shared/workspace/persistence-public/inc/rest-public.php.mustache +1 -1
  28. package/templates/_shared/workspace/persistence-public/server.php.mustache +159 -0
  29. package/templates/persistence/src/edit.tsx.mustache +1 -1
  30. package/templates/persistence/src/render.php.mustache +37 -43
  31. package/templates/persistence/src/types.ts.mustache +13 -9
@@ -197,6 +197,105 @@ function {{phpPrefix}}_build_state_response( $post_id, $resource_key, $count ) {
197
197
  );
198
198
  }
199
199
 
200
+ function {{phpPrefix}}_block_tree_has_resource_key( $blocks, $block_name, $resource_key ) {
201
+ if ( ! is_array( $blocks ) ) {
202
+ return false;
203
+ }
204
+
205
+ foreach ( $blocks as $block ) {
206
+ if ( ! is_array( $block ) ) {
207
+ continue;
208
+ }
209
+
210
+ if ( (string) ( $block['blockName'] ?? '' ) === $block_name ) {
211
+ $attributes = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : array();
212
+ $candidate_resource_key = array_key_exists( 'resourceKey', $attributes )
213
+ ? (string) $attributes['resourceKey']
214
+ : 'primary';
215
+ if ( (string) $resource_key === $candidate_resource_key ) {
216
+ return true;
217
+ }
218
+ }
219
+
220
+ if (
221
+ isset( $block['innerBlocks'] ) &&
222
+ {{phpPrefix}}_block_tree_has_resource_key( $block['innerBlocks'], $block_name, $resource_key )
223
+ ) {
224
+ return true;
225
+ }
226
+ }
227
+
228
+ return false;
229
+ }
230
+
231
+ function {{phpPrefix}}_get_rendered_block_instance_key( $post_id, $block_name, $resource_key ) {
232
+ return 'wpt_pri_' . md5( implode( '|', array( (string) $block_name, (int) $post_id, (string) $resource_key ) ) );
233
+ }
234
+
235
+ function {{phpPrefix}}_record_rendered_block_instance( $post_id, $block_name, $resource_key ) {
236
+ if ( $post_id <= 0 || '' === (string) $resource_key || '' === (string) $block_name ) {
237
+ return;
238
+ }
239
+
240
+ set_transient(
241
+ {{phpPrefix}}_get_rendered_block_instance_key( $post_id, $block_name, $resource_key ),
242
+ 1,
243
+ 5 * MINUTE_IN_SECONDS
244
+ );
245
+ }
246
+
247
+ function {{phpPrefix}}_has_rendered_block_instance( $post_id, $resource_key ) {
248
+ if ( $post_id <= 0 || '' === (string) $resource_key ) {
249
+ return false;
250
+ }
251
+
252
+ if (
253
+ false !== get_transient(
254
+ {{phpPrefix}}_get_rendered_block_instance_key(
255
+ $post_id,
256
+ '{{namespace}}/{{slugKebabCase}}',
257
+ $resource_key
258
+ )
259
+ )
260
+ ) {
261
+ return true;
262
+ }
263
+
264
+ $post = get_post( $post_id );
265
+ if ( ! ( $post instanceof WP_Post ) ) {
266
+ return false;
267
+ }
268
+
269
+ return {{phpPrefix}}_block_tree_has_resource_key(
270
+ parse_blocks( (string) $post->post_content ),
271
+ '{{namespace}}/{{slugKebabCase}}',
272
+ (string) $resource_key
273
+ );
274
+ }
275
+
276
+ function {{phpPrefix}}_build_bootstrap_response( $post_id, $resource_key ) {
277
+ $post = get_post( $post_id );
278
+ $can_read_post =
279
+ $post instanceof WP_Post &&
280
+ (
281
+ is_post_publicly_viewable( $post ) ||
282
+ current_user_can( 'read_post', $post->ID )
283
+ );
284
+ $can_write = $post_id > 0 &&
285
+ is_user_logged_in() &&
286
+ $can_read_post &&
287
+ {{phpPrefix}}_has_rendered_block_instance( (int) $post_id, (string) $resource_key );
288
+ $response = array(
289
+ 'canWrite' => $can_write,
290
+ );
291
+
292
+ if ( $can_write ) {
293
+ $response['restNonce'] = wp_create_nonce( 'wp_rest' );
294
+ }
295
+
296
+ return $response;
297
+ }
298
+
200
299
  // Route handlers are the main product-level extension point for request/response shaping.
201
300
  function {{phpPrefix}}_handle_get_state( WP_REST_Request $request ) {
202
301
  $payload = {{phpPrefix}}_validate_and_sanitize_request(
@@ -223,6 +322,34 @@ function {{phpPrefix}}_handle_get_state( WP_REST_Request $request ) {
223
322
  );
224
323
  }
225
324
 
325
+ function {{phpPrefix}}_handle_get_bootstrap( WP_REST_Request $request ) {
326
+ $payload = {{phpPrefix}}_validate_and_sanitize_request(
327
+ array(
328
+ 'postId' => $request->get_param( 'postId' ),
329
+ 'resourceKey' => $request->get_param( 'resourceKey' ),
330
+ ),
331
+ {{phpPrefix}}_get_rest_build_dir(),
332
+ 'bootstrap-query',
333
+ 'query'
334
+ );
335
+
336
+ if ( is_wp_error( $payload ) ) {
337
+ return $payload;
338
+ }
339
+
340
+ $response = rest_ensure_response(
341
+ {{phpPrefix}}_build_bootstrap_response(
342
+ (int) $payload['postId'],
343
+ (string) $payload['resourceKey']
344
+ )
345
+ );
346
+
347
+ $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0, s-maxage=0' );
348
+ $response->header( 'Pragma', 'no-cache' );
349
+ $response->header( 'Vary', 'Cookie' );
350
+ return $response;
351
+ }
352
+
226
353
  function {{phpPrefix}}_handle_write_state( WP_REST_Request $request ) {
227
354
  $payload = {{phpPrefix}}_validate_and_sanitize_request(
228
355
  $request->get_json_params(),
@@ -272,6 +399,18 @@ function {{phpPrefix}}_register_routes() {
272
399
  ),
273
400
  )
274
401
  );
402
+
403
+ register_rest_route(
404
+ '{{namespace}}/v1',
405
+ '/{{slugKebabCase}}/bootstrap',
406
+ array(
407
+ array(
408
+ 'methods' => WP_REST_Server::READABLE,
409
+ 'callback' => '{{phpPrefix}}_handle_get_bootstrap',
410
+ 'permission_callback' => '__return_true',
411
+ ),
412
+ )
413
+ );
275
414
  }
276
415
 
277
416
  function {{phpPrefix}}_register_block() {
@@ -28,9 +28,15 @@ const REST_ENDPOINT_MANIFEST = defineEndpointManifest( {
28
28
  'state-query': {
29
29
  sourceTypeName: '{{pascalCase}}StateQuery',
30
30
  },
31
+ 'bootstrap-query': {
32
+ sourceTypeName: '{{pascalCase}}BootstrapQuery',
33
+ },
31
34
  'write-state-request': {
32
35
  sourceTypeName: '{{pascalCase}}WriteStateRequest',
33
36
  },
37
+ 'bootstrap-response': {
38
+ sourceTypeName: '{{pascalCase}}BootstrapResponse',
39
+ },
34
40
  'state-response': {
35
41
  sourceTypeName: '{{pascalCase}}StateResponse',
36
42
  },
@@ -59,6 +65,16 @@ const REST_ENDPOINT_MANIFEST = defineEndpointManifest( {
59
65
  mechanism: '{{restWriteAuthMechanism}}',
60
66
  },
61
67
  },
68
+ {
69
+ auth: 'public',
70
+ method: 'GET',
71
+ operationId: 'get{{pascalCase}}Bootstrap',
72
+ path: '/{{namespace}}/v1/{{slugKebabCase}}/bootstrap',
73
+ queryContract: 'bootstrap-query',
74
+ responseContract: 'bootstrap-response',
75
+ summary: 'Read fresh session bootstrap state for the current viewer.',
76
+ tags: [ '{{title}}' ],
77
+ },
62
78
  ],
63
79
  info: {
64
80
  title: '{{title}} REST API',
@@ -5,6 +5,11 @@ export interface {{pascalCase}}StateQuery {
5
5
  resourceKey: string & tags.MinLength< 1 > & tags.MaxLength< 100 >;
6
6
  }
7
7
 
8
+ export interface {{pascalCase}}BootstrapQuery {
9
+ postId: number & tags.Type< 'uint32' >;
10
+ resourceKey: string & tags.MinLength< 1 > & tags.MaxLength< 100 >;
11
+ }
12
+
8
13
  export interface {{pascalCase}}WriteStateRequest {
9
14
  postId: number & tags.Type< 'uint32' >;
10
15
  {{publicWriteRequestIdDeclaration}}
@@ -13,6 +18,11 @@ export interface {{pascalCase}}WriteStateRequest {
13
18
  delta?: number & tags.Minimum< 1 > & tags.Type< 'uint32' > & tags.Default< 1 >;
14
19
  }
15
20
 
21
+ export interface {{pascalCase}}BootstrapResponse {
22
+ canWrite: boolean;
23
+ {{bootstrapCredentialDeclarations}}
24
+ }
25
+
16
26
  export interface {{pascalCase}}StateResponse {
17
27
  postId: number & tags.Type< 'uint32' >;
18
28
  resourceKey: string & tags.MinLength< 1 > & tags.MaxLength< 100 >;
@@ -1,68 +1,81 @@
1
1
  import {
2
2
  callEndpoint,
3
- resolveRestRouteUrl,
4
3
  } from '@wp-typia/rest';
5
4
 
6
5
  import {
6
+ type {{pascalCase}}BootstrapQuery,
7
7
  type {{pascalCase}}StateQuery,
8
8
  type {{pascalCase}}WriteStateRequest,
9
9
  } from './api-types';
10
10
  import {
11
+ get{{pascalCase}}BootstrapEndpoint,
11
12
  get{{pascalCase}}StateEndpoint,
12
13
  write{{pascalCase}}StateEndpoint,
13
14
  } from './api-client';
15
+ import {
16
+ resolveTransportCallOptions,
17
+ type PersistenceTransportOptions,
18
+ } from './transport';
14
19
 
15
- export function resolveRestNonce( fallback?: string ): string | undefined {
16
- if ( typeof fallback === 'string' && fallback.length > 0 ) {
17
- return fallback;
18
- }
19
-
20
- if ( typeof window === 'undefined' ) {
21
- return undefined;
22
- }
23
-
24
- const wpApiSettings = ( window as typeof window & {
25
- wpApiSettings?: { nonce?: string };
26
- } ).wpApiSettings;
27
-
28
- return typeof wpApiSettings?.nonce === 'string' && wpApiSettings.nonce.length > 0
29
- ? wpApiSettings.nonce
30
- : undefined;
31
- }
20
+ export const bootstrapEndpoint = {
21
+ ...get{{pascalCase}}BootstrapEndpoint,
22
+ };
32
23
 
33
24
  export const stateEndpoint = {
34
25
  ...get{{pascalCase}}StateEndpoint,
35
- buildRequestOptions: () => ( {
36
- url: resolveRestRouteUrl( get{{pascalCase}}StateEndpoint.path ),
37
- } ),
38
26
  };
39
27
 
40
28
  export const writeStateEndpoint = {
41
29
  ...write{{pascalCase}}StateEndpoint,
42
- buildRequestOptions: () => ( {
43
- url: resolveRestRouteUrl( write{{pascalCase}}StateEndpoint.path ),
44
- } ),
45
30
  };
46
31
 
47
32
  export function fetchState(
48
- request: {{pascalCase}}StateQuery
33
+ request: {{pascalCase}}StateQuery,
34
+ options: PersistenceTransportOptions = {}
49
35
  ) {
50
- return callEndpoint( stateEndpoint, request );
36
+ return callEndpoint(
37
+ stateEndpoint,
38
+ request,
39
+ resolveTransportCallOptions(
40
+ options.transportTarget ?? 'frontend',
41
+ 'read',
42
+ stateEndpoint,
43
+ request,
44
+ options
45
+ )
46
+ );
47
+ }
48
+
49
+ export function fetchBootstrap(
50
+ request: {{pascalCase}}BootstrapQuery,
51
+ options: PersistenceTransportOptions = {}
52
+ ) {
53
+ return callEndpoint(
54
+ bootstrapEndpoint,
55
+ request,
56
+ resolveTransportCallOptions(
57
+ options.transportTarget ?? 'frontend',
58
+ 'read',
59
+ bootstrapEndpoint,
60
+ request,
61
+ options
62
+ )
63
+ );
51
64
  }
52
65
 
53
66
  export function writeState(
54
67
  request: {{pascalCase}}WriteStateRequest,
55
- restNonce?: string
68
+ options: PersistenceTransportOptions = {}
56
69
  ) {
57
- const nonce = resolveRestNonce( restNonce );
58
-
59
- return callEndpoint( writeStateEndpoint, request, {
60
- requestOptions: nonce
61
- ? {
62
- headers: {
63
- 'X-WP-Nonce': nonce,
64
- },
65
- }
66
- : undefined,
67
- } );
70
+ return callEndpoint(
71
+ writeStateEndpoint,
72
+ request,
73
+ resolveTransportCallOptions(
74
+ options.transportTarget ?? 'frontend',
75
+ 'write',
76
+ writeStateEndpoint,
77
+ request,
78
+ options
79
+ )
80
+ );
68
81
  }
@@ -6,31 +6,21 @@ import {
6
6
  } from '@wp-typia/rest/react';
7
7
 
8
8
  import type {
9
+ {{pascalCase}}BootstrapQuery,
10
+ {{pascalCase}}BootstrapResponse,
9
11
  {{pascalCase}}StateQuery,
10
12
  {{pascalCase}}StateResponse,
11
13
  {{pascalCase}}WriteStateRequest,
12
14
  } from './api-types';
13
15
  import {
14
- resolveRestNonce,
16
+ bootstrapEndpoint,
15
17
  stateEndpoint,
16
18
  writeStateEndpoint,
17
19
  } from './api';
18
-
19
- {{#isAuthenticatedPersistencePolicy}}
20
- function buildWriteCallOptions( restNonce?: string ) {
21
- const nonce = resolveRestNonce( restNonce );
22
-
23
- return nonce
24
- ? {
25
- requestOptions: {
26
- headers: {
27
- 'X-WP-Nonce': nonce,
28
- },
29
- },
30
- }
31
- : undefined;
32
- }
33
- {{/isAuthenticatedPersistencePolicy}}
20
+ import {
21
+ resolveTransportCallOptions,
22
+ type PersistenceTransportTarget,
23
+ } from './transport';
34
24
 
35
25
  interface WriteStateMutationContext< Context > {
36
26
  previous:
@@ -49,9 +39,22 @@ export interface Use{{pascalCase}}StateQueryOptions<
49
39
  >,
50
40
  'resolveCallOptions'
51
41
  > {
52
- {{#isAuthenticatedPersistencePolicy}}
53
42
  restNonce?: string;
54
- {{/isAuthenticatedPersistencePolicy}}
43
+ transportTarget?: PersistenceTransportTarget;
44
+ }
45
+
46
+ export interface Use{{pascalCase}}BootstrapQueryOptions<
47
+ Selected = {{pascalCase}}BootstrapResponse,
48
+ > extends Omit<
49
+ UseEndpointQueryOptions<
50
+ {{pascalCase}}BootstrapQuery,
51
+ {{pascalCase}}BootstrapResponse,
52
+ Selected
53
+ >,
54
+ 'resolveCallOptions'
55
+ > {
56
+ restNonce?: string;
57
+ transportTarget?: PersistenceTransportTarget;
55
58
  }
56
59
 
57
60
  export interface UseWrite{{pascalCase}}StateMutationOptions<
@@ -62,7 +65,7 @@ export interface UseWrite{{pascalCase}}StateMutationOptions<
62
65
  {{pascalCase}}StateResponse,
63
66
  WriteStateMutationContext< Context >
64
67
  >,
65
- 'invalidate' | 'onError' | 'onMutate'{{#isAuthenticatedPersistencePolicy}} | 'resolveCallOptions'{{/isAuthenticatedPersistencePolicy}}
68
+ 'invalidate' | 'onError' | 'onMutate' | 'resolveCallOptions'
66
69
  > {
67
70
  onError?: (
68
71
  error: unknown,
@@ -74,9 +77,8 @@ export interface UseWrite{{pascalCase}}StateMutationOptions<
74
77
  request: {{pascalCase}}WriteStateRequest,
75
78
  client: import('@wp-typia/rest/react').EndpointDataClient
76
79
  ) => Context | Promise<Context>;
77
- {{#isAuthenticatedPersistencePolicy}}
78
80
  restNonce?: string;
79
- {{/isAuthenticatedPersistencePolicy}}
81
+ transportTarget?: PersistenceTransportTarget;
80
82
  }
81
83
 
82
84
  export function use{{pascalCase}}StateQuery<
@@ -85,20 +87,54 @@ export function use{{pascalCase}}StateQuery<
85
87
  request: {{pascalCase}}StateQuery,
86
88
  options: Use{{pascalCase}}StateQueryOptions< Selected > = {}
87
89
  ) {
88
- {{#isAuthenticatedPersistencePolicy}}
89
90
  const {
90
91
  restNonce,
92
+ transportTarget = 'editor',
91
93
  ...queryOptions
92
94
  } = options;
93
95
 
94
96
  return useEndpointQuery( stateEndpoint, request, {
95
97
  ...queryOptions,
96
- resolveCallOptions: () => buildWriteCallOptions( restNonce ),
98
+ resolveCallOptions: () =>
99
+ resolveTransportCallOptions(
100
+ transportTarget,
101
+ 'read',
102
+ stateEndpoint,
103
+ request,
104
+ {
105
+ restNonce,
106
+ transportTarget,
107
+ }
108
+ ),
109
+ } );
110
+ }
111
+
112
+ export function use{{pascalCase}}BootstrapQuery<
113
+ Selected = {{pascalCase}}BootstrapResponse,
114
+ >(
115
+ request: {{pascalCase}}BootstrapQuery,
116
+ options: Use{{pascalCase}}BootstrapQueryOptions< Selected > = {}
117
+ ) {
118
+ const {
119
+ restNonce,
120
+ transportTarget = 'editor',
121
+ ...queryOptions
122
+ } = options;
123
+
124
+ return useEndpointQuery( bootstrapEndpoint, request, {
125
+ ...queryOptions,
126
+ resolveCallOptions: () =>
127
+ resolveTransportCallOptions(
128
+ transportTarget,
129
+ 'read',
130
+ bootstrapEndpoint,
131
+ request,
132
+ {
133
+ restNonce,
134
+ transportTarget,
135
+ }
136
+ ),
97
137
  } );
98
- {{/isAuthenticatedPersistencePolicy}}
99
- {{^isAuthenticatedPersistencePolicy}}
100
- return useEndpointQuery( stateEndpoint, request, options );
101
- {{/isAuthenticatedPersistencePolicy}}
102
138
  }
103
139
 
104
140
  export function useWrite{{pascalCase}}StateMutation<
@@ -109,9 +145,8 @@ export function useWrite{{pascalCase}}StateMutation<
109
145
  const {
110
146
  onError,
111
147
  onMutate,
112
- {{#isAuthenticatedPersistencePolicy}}
113
148
  restNonce,
114
- {{/isAuthenticatedPersistencePolicy}}
149
+ transportTarget = 'editor',
115
150
  ...mutationOptions
116
151
  } = options;
117
152
 
@@ -185,8 +220,16 @@ export function useWrite{{pascalCase}}StateMutation<
185
220
  userContext,
186
221
  };
187
222
  },
188
- {{#isAuthenticatedPersistencePolicy}}
189
- resolveCallOptions: () => buildWriteCallOptions( restNonce ),
190
- {{/isAuthenticatedPersistencePolicy}}
223
+ resolveCallOptions: ( request ) =>
224
+ resolveTransportCallOptions(
225
+ transportTarget,
226
+ 'write',
227
+ writeStateEndpoint,
228
+ request,
229
+ {
230
+ restNonce,
231
+ transportTarget,
232
+ }
233
+ ),
191
234
  } );
192
235
  }