@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
@@ -37,12 +37,12 @@ export function getTemplateSourceOfTruthNote(templateId, { compoundPersistenceEn
37
37
  if (templateId === "compound") {
38
38
  const compoundBase = "`src/blocks/*/types.ts` files remain the source of truth for each block's `block.json`, `typia.manifest.json`, and `typia-validator.php`. Fresh scaffolds include starter `typia.manifest.json` files so editor imports resolve before the first sync.";
39
39
  if (compoundPersistenceEnabled) {
40
- return `${compoundBase} For persistence-enabled parents, \`src/blocks/*/api-types.ts\` files remain the source of truth for \`src/blocks/*/api-schemas/*\` when you run \`sync-rest\`.`;
40
+ return `${compoundBase} For persistence-enabled parents, \`src/blocks/*/api-types.ts\` files remain the source of truth for \`src/blocks/*/api-schemas/*\` when you run \`sync-rest\`, while \`src/blocks/*/transport.ts\` is the first-class transport seam for editor and frontend requests.`;
41
41
  }
42
42
  return compoundBase;
43
43
  }
44
44
  if (templateId === "persistence") {
45
- return "`src/types.ts` remains the source of truth for `block.json`, `typia.manifest.json`, and `typia-validator.php`. Fresh scaffolds include a starter `typia.manifest.json` so editor imports resolve before the first sync. `src/api-types.ts` remains the source of truth for `src/api-schemas/*` when you run `sync-rest`. This scaffold is intentionally server-rendered: `src/render.php` is the canonical frontend entry, and `src/save.tsx` returns `null` so PHP can inject post context, storage-backed state, and write-policy bootstrap data before hydration.";
45
+ return "`src/types.ts` remains the source of truth for `block.json`, `typia.manifest.json`, and `typia-validator.php`. Fresh scaffolds include a starter `typia.manifest.json` so editor imports resolve before the first sync. `src/api-types.ts` remains the source of truth for `src/api-schemas/*` when you run `sync-rest`, while `src/transport.ts` is the first-class transport seam for editor and frontend requests. This scaffold is intentionally server-rendered: `src/render.php` is the canonical frontend entry, `src/save.tsx` returns `null`, and session-only write data now refreshes through the dedicated `/bootstrap` endpoint after hydration instead of being frozen into markup.";
46
46
  }
47
47
  return "`src/types.ts` remains the source of truth for `block.json`, `typia.manifest.json`, and `typia-validator.php`. Fresh scaffolds include a starter `typia.manifest.json` so editor imports resolve before the first sync. The basic scaffold stays static by design: `src/render.php` is only an opt-in server placeholder, `src/save.tsx` remains the canonical frontend output, and the generated webpack config keeps the current `@wordpress/scripts` CommonJS baseline unless you intentionally add `render` to `block.json`.";
48
48
  }
@@ -61,13 +61,14 @@ ${formatRunScript(packageManager, "add-child", '--slug faq-item --title "FAQ Ite
61
61
 
62
62
  This scaffolds a new hidden child block type, updates \`scripts/block-config.ts\` and \`src/blocks/*/children.ts\`, and leaves the default seeded child template unchanged.`;
63
63
  }
64
- function formatPhpRestExtensionPointsSection({ apiTypesPath, extraNote, mainPhpPath, mainPhpScope, }) {
64
+ function formatPhpRestExtensionPointsSection({ apiTypesPath, extraNote, mainPhpPath, mainPhpScope, transportPath, }) {
65
65
  const schemaJsonGlob = apiTypesPath.replace(/api-types\.ts$/u, "api-schemas/*.schema.json");
66
66
  const perContractOpenApiGlob = apiTypesPath.replace(/api-types\.ts$/u, "api-schemas/*.openapi.json");
67
67
  const aggregateOpenApiPath = apiTypesPath.replace(/api-types\.ts$/u, "api.openapi.json");
68
68
  const lines = [
69
69
  `- Edit \`${mainPhpPath}\` when you need to ${mainPhpScope}.`,
70
70
  "- Edit `inc/rest-auth.php` or `inc/rest-public.php` when you need to customize write permissions or token/request-id/nonce checks for the selected policy.",
71
+ `- Edit \`${transportPath}\` when you need to switch between direct WordPress REST and a contract-compatible proxy or BFF without changing the endpoint contracts.`,
71
72
  `- Keep \`${apiTypesPath}\` as the source of truth for request and response contracts, then regenerate \`${schemaJsonGlob}\`, per-contract \`${perContractOpenApiGlob}\`, and \`${aggregateOpenApiPath}\` with \`sync-rest\`.`,
72
73
  "- Avoid hand-editing generated schema and OpenAPI artifacts unless you are debugging generated output; they are meant to be regenerated from TypeScript contracts.",
73
74
  ];
@@ -85,14 +86,17 @@ export function getPhpRestExtensionPointsSection(templateId, { compoundPersisten
85
86
  apiTypesPath: "src/api-types.ts",
86
87
  mainPhpPath: `${slug}.php`,
87
88
  mainPhpScope: "change storage helpers, route handlers, response shaping, or route registration",
89
+ transportPath: "src/transport.ts",
90
+ extraNote: "Keep durable state on the `/state` endpoints and treat the dedicated `/bootstrap` endpoint as the place to return fresh session-only write access data such as nonces or public write tokens.",
88
91
  });
89
92
  }
90
93
  if (templateId === "compound" && compoundPersistenceEnabled) {
91
94
  return formatPhpRestExtensionPointsSection({
92
95
  apiTypesPath: `src/blocks/${slug}/api-types.ts`,
93
- extraNote: "The hidden child block does not own REST routes or storage.",
96
+ extraNote: "The hidden child block does not own REST routes or storage. Keep durable parent-block state on the `/state` endpoints and return fresh session-only write access data from the dedicated `/bootstrap` endpoint.",
94
97
  mainPhpPath: `${slug}.php`,
95
98
  mainPhpScope: "change parent-block storage helpers, route handlers, response shaping, or route registration",
99
+ transportPath: `src/blocks/${slug}/transport.ts`,
96
100
  });
97
101
  }
98
102
  return null;
@@ -56,6 +56,7 @@ export interface ScaffoldTemplateVariables extends Record<string, string> {
56
56
  phpPrefixUpper: string;
57
57
  isAuthenticatedPersistencePolicy: "false" | "true";
58
58
  isPublicPersistencePolicy: "false" | "true";
59
+ bootstrapCredentialDeclarations: string;
59
60
  publicWriteRequestIdDeclaration: string;
60
61
  restPackageVersion: string;
61
62
  restWriteAuthIntent: "authenticated" | "public-write-protected";
@@ -262,6 +262,9 @@ export function getTemplateVariables(templateId, answers) {
262
262
  frontendCssClassName: buildFrontendCssClassName(cssClassName),
263
263
  isAuthenticatedPersistencePolicy: persistencePolicy === "authenticated" ? "true" : "false",
264
264
  isPublicPersistencePolicy: persistencePolicy === "public" ? "true" : "false",
265
+ bootstrapCredentialDeclarations: persistencePolicy === "public"
266
+ ? "publicWriteExpiresAt?: number & tags.Type< 'uint32' >;\n\tpublicWriteToken?: string & tags.MinLength< 1 > & tags.MaxLength< 512 >;"
267
+ : "restNonce?: string & tags.MinLength< 1 > & tags.MaxLength< 128 >;",
265
268
  keyword: slug.replace(/-/g, " "),
266
269
  namespace,
267
270
  needsMigration: "{{needsMigration}}",
@@ -59,6 +59,7 @@ export interface OpenApiResponse extends JsonSchemaObject {
59
59
  "application/json": OpenApiMediaType;
60
60
  };
61
61
  description: string;
62
+ headers?: Record<string, JsonSchemaObject>;
62
63
  }
63
64
  /**
64
65
  * Header-based security scheme used by authenticated WordPress REST routes.
@@ -149,15 +149,35 @@ function applyProjectedTypeTag(schema, typeTag, path) {
149
149
  throw new Error(`Unsupported wp-typia schema type tag "${typeTag}" at "${path}".`);
150
150
  }
151
151
  }
152
+ function canProjectTypeTag(typeTag) {
153
+ switch (typeTag) {
154
+ case "uint32":
155
+ case "int32":
156
+ case "float":
157
+ case "double":
158
+ return true;
159
+ default:
160
+ return false;
161
+ }
162
+ }
152
163
  function projectSchemaArrayItemsForAiStructuredOutput(items, path) {
153
164
  return items.map((item, index) => projectSchemaObjectForAiStructuredOutput(item, `${path}/${index}`));
154
165
  }
166
+ function projectSchemaArrayItemsForRest(items, path) {
167
+ return items.map((item, index) => projectSchemaObjectForRest(item, `${path}/${index}`));
168
+ }
155
169
  function projectSchemaPropertyMapForAiStructuredOutput(properties, path) {
156
170
  return Object.fromEntries(Object.entries(properties).map(([key, value]) => [
157
171
  key,
158
172
  projectSchemaObjectForAiStructuredOutput(value, `${path}/${key}`),
159
173
  ]));
160
174
  }
175
+ function projectSchemaPropertyMapForRest(properties, path) {
176
+ return Object.fromEntries(Object.entries(properties).map(([key, value]) => [
177
+ key,
178
+ projectSchemaObjectForRest(value, `${path}/${key}`),
179
+ ]));
180
+ }
161
181
  function projectSchemaObjectForAiStructuredOutput(node, path) {
162
182
  const projectedNode = cloneJsonSchemaNode(node);
163
183
  const rawTypeTag = projectedNode[WP_TYPIA_OPENAPI_EXTENSION_KEYS.TYPE_TAG];
@@ -188,6 +208,126 @@ function projectSchemaObjectForAiStructuredOutput(node, path) {
188
208
  }
189
209
  return projectedNode;
190
210
  }
211
+ function projectSchemaObjectForRest(node, path) {
212
+ const projectedNode = cloneJsonSchemaNode(node);
213
+ const rawTypeTag = projectedNode[WP_TYPIA_OPENAPI_EXTENSION_KEYS.TYPE_TAG];
214
+ if (typeof rawTypeTag === "string" && canProjectTypeTag(rawTypeTag)) {
215
+ applyProjectedTypeTag(projectedNode, rawTypeTag, path);
216
+ }
217
+ for (const key of Object.keys(projectedNode)) {
218
+ const child = projectedNode[key];
219
+ if (Array.isArray(child)) {
220
+ projectedNode[key] = child.every(isJsonSchemaObject)
221
+ ? projectSchemaArrayItemsForRest(child, `${path}/${key}`)
222
+ : child;
223
+ continue;
224
+ }
225
+ if (!isJsonSchemaObject(child)) {
226
+ continue;
227
+ }
228
+ if (key === "properties") {
229
+ projectedNode[key] = projectSchemaPropertyMapForRest(child, `${path}/${key}`);
230
+ continue;
231
+ }
232
+ projectedNode[key] = projectSchemaObjectForRest(child, `${path}/${key}`);
233
+ }
234
+ applyProjectedBootstrapContract(projectedNode);
235
+ return projectedNode;
236
+ }
237
+ function applyProjectedBootstrapContract(schema) {
238
+ if (schema.type !== "object" || !isJsonSchemaObject(schema.properties)) {
239
+ return;
240
+ }
241
+ const properties = schema.properties;
242
+ const buildRequiredPropertyObject = (requiredKeys) => Object.fromEntries(requiredKeys.map((requiredKey) => [requiredKey, properties[requiredKey] ?? {}]));
243
+ if (properties.canWrite?.type !== "boolean") {
244
+ return;
245
+ }
246
+ const allOf = Array.isArray(schema.allOf)
247
+ ? [...schema.allOf]
248
+ : [];
249
+ const canWriteIsTrue = {
250
+ properties: {
251
+ canWrite: {
252
+ const: true,
253
+ },
254
+ },
255
+ required: ["canWrite"],
256
+ };
257
+ const hasRestNonce = properties.restNonce?.type === "string";
258
+ const hasPublicWriteCredential = properties.publicWriteToken?.type === "string" &&
259
+ isJsonSchemaObject(properties.publicWriteExpiresAt);
260
+ if (hasRestNonce && hasPublicWriteCredential) {
261
+ allOf.push({
262
+ if: canWriteIsTrue,
263
+ then: {
264
+ anyOf: [
265
+ {
266
+ properties: buildRequiredPropertyObject(["restNonce"]),
267
+ required: ["restNonce"],
268
+ },
269
+ {
270
+ properties: buildRequiredPropertyObject([
271
+ "publicWriteExpiresAt",
272
+ "publicWriteToken",
273
+ ]),
274
+ required: ["publicWriteExpiresAt", "publicWriteToken"],
275
+ },
276
+ ],
277
+ },
278
+ else: {
279
+ not: {
280
+ anyOf: [
281
+ {
282
+ properties: buildRequiredPropertyObject(["restNonce"]),
283
+ required: ["restNonce"],
284
+ },
285
+ {
286
+ properties: buildRequiredPropertyObject(["publicWriteToken"]),
287
+ required: ["publicWriteToken"],
288
+ },
289
+ ],
290
+ },
291
+ },
292
+ });
293
+ }
294
+ if (hasRestNonce && !hasPublicWriteCredential) {
295
+ allOf.push({
296
+ if: canWriteIsTrue,
297
+ then: {
298
+ properties: buildRequiredPropertyObject(["restNonce"]),
299
+ required: ["restNonce"],
300
+ },
301
+ else: {
302
+ not: {
303
+ properties: buildRequiredPropertyObject(["restNonce"]),
304
+ required: ["restNonce"],
305
+ },
306
+ },
307
+ });
308
+ }
309
+ if (hasPublicWriteCredential && !hasRestNonce) {
310
+ allOf.push({
311
+ if: canWriteIsTrue,
312
+ then: {
313
+ properties: buildRequiredPropertyObject([
314
+ "publicWriteExpiresAt",
315
+ "publicWriteToken",
316
+ ]),
317
+ required: ["publicWriteExpiresAt", "publicWriteToken"],
318
+ },
319
+ else: {
320
+ not: {
321
+ properties: buildRequiredPropertyObject(["publicWriteToken"]),
322
+ required: ["publicWriteToken"],
323
+ },
324
+ },
325
+ });
326
+ }
327
+ if (allOf.length > 0) {
328
+ schema.allOf = allOf;
329
+ }
330
+ }
191
331
  function manifestUnionToJsonSchema(union) {
192
332
  const oneOf = Object.entries(union.branches).map(([branchKey, branch]) => {
193
333
  if (branch.ts.kind !== "object") {
@@ -305,7 +445,7 @@ export function manifestToJsonSchema(doc) {
305
445
  */
306
446
  export function projectJsonSchemaDocument(schema, options) {
307
447
  if (options.profile === "rest") {
308
- return cloneJsonSchemaNode(schema);
448
+ return projectSchemaObjectForRest(schema, "#");
309
449
  }
310
450
  if (options.profile === "ai-structured-output") {
311
451
  return projectSchemaObjectForAiStructuredOutput(schema, "#");
@@ -321,10 +461,14 @@ export function projectJsonSchemaDocument(schema, options) {
321
461
  */
322
462
  export function manifestToOpenApi(doc, info = {}) {
323
463
  const schemaName = doc.sourceType ?? "TypiaDocument";
464
+ const projectedSchema = projectJsonSchemaDocument(manifestToJsonSchema(doc), {
465
+ profile: "rest",
466
+ });
467
+ delete projectedSchema.$schema;
324
468
  return {
325
469
  components: {
326
470
  schemas: {
327
- [schemaName]: manifestToJsonSchema(doc),
471
+ [schemaName]: projectedSchema,
328
472
  },
329
473
  },
330
474
  info: {
@@ -486,7 +630,36 @@ function buildQueryParameters(contract) {
486
630
  schema: manifestAttributeToJsonSchema(attribute),
487
631
  }));
488
632
  }
489
- function createSuccessResponse(schemaName) {
633
+ function createBootstrapResponseHeaders(normalizedAuth) {
634
+ const headers = {
635
+ "Cache-Control": {
636
+ description: "Must be non-cacheable for fresh bootstrap write/session state.",
637
+ schema: {
638
+ type: "string",
639
+ example: "private, no-store, no-cache, must-revalidate",
640
+ },
641
+ },
642
+ Pragma: {
643
+ description: "Legacy non-cacheable bootstrap response directive.",
644
+ schema: {
645
+ type: "string",
646
+ example: "no-cache",
647
+ },
648
+ },
649
+ };
650
+ if (normalizedAuth.wordpressAuth?.mechanism ===
651
+ WP_TYPIA_OPENAPI_LITERALS.WORDPRESS_REST_NONCE_MECHANISM) {
652
+ headers.Vary = {
653
+ description: "Viewer-aware bootstrap responses should vary on cookie-backed auth state.",
654
+ schema: {
655
+ type: "string",
656
+ example: "Cookie",
657
+ },
658
+ };
659
+ }
660
+ return headers;
661
+ }
662
+ function createSuccessResponse(schemaName, headers) {
490
663
  return {
491
664
  content: {
492
665
  [WP_TYPIA_OPENAPI_LITERALS.JSON_CONTENT_TYPE]: {
@@ -494,14 +667,18 @@ function createSuccessResponse(schemaName) {
494
667
  },
495
668
  },
496
669
  description: WP_TYPIA_OPENAPI_LITERALS.SUCCESS_RESPONSE_DESCRIPTION,
670
+ ...(headers ? { headers } : {}),
497
671
  };
498
672
  }
499
673
  function buildEndpointOpenApiOperation(endpoint, contracts) {
500
674
  const normalizedAuth = normalizeEndpointAuthDefinition(endpoint);
675
+ const isBootstrapEndpoint = endpoint.path.endsWith("/bootstrap");
501
676
  const operation = {
502
677
  operationId: endpoint.operationId,
503
678
  responses: {
504
- "200": createSuccessResponse(getContractSchemaName(endpoint.responseContract, contracts[endpoint.responseContract], endpoint, "response")),
679
+ "200": createSuccessResponse(getContractSchemaName(endpoint.responseContract, contracts[endpoint.responseContract], endpoint, "response"), isBootstrapEndpoint
680
+ ? createBootstrapResponseHeaders(normalizedAuth)
681
+ : undefined),
505
682
  },
506
683
  tags: [...endpoint.tags],
507
684
  [WP_TYPIA_OPENAPI_EXTENSION_KEYS.AUTH_INTENT]: normalizedAuth.auth,
@@ -554,10 +731,13 @@ function buildEndpointOpenApiOperation(endpoint, contracts) {
554
731
  */
555
732
  export function buildEndpointOpenApiDocument(options) {
556
733
  const contractEntries = Object.entries(options.contracts);
557
- const schemas = Object.fromEntries(contractEntries.map(([contractKey, contract]) => [
558
- getContractSchemaName(contractKey, contract),
559
- manifestToJsonSchema(contract.document),
560
- ]));
734
+ const schemas = Object.fromEntries(contractEntries.map(([contractKey, contract]) => {
735
+ const projectedSchema = projectJsonSchemaDocument(manifestToJsonSchema(contract.document), {
736
+ profile: "rest",
737
+ });
738
+ delete projectedSchema.$schema;
739
+ return [getContractSchemaName(contractKey, contract), projectedSchema];
740
+ }));
561
741
  const paths = {};
562
742
  const topLevelTags = [...new Set(options.endpoints.flatMap((endpoint) => endpoint.tags))]
563
743
  .filter((tag) => typeof tag === "string" && tag.length > 0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-typia/project-tools",
3
- "version": "0.13.4",
3
+ "version": "0.15.0",
4
4
  "description": "Project orchestration and programmatic tooling for wp-typia",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "type": "module",
@@ -9,9 +9,15 @@ export const BLOCKS = [
9
9
  'state-query': {
10
10
  sourceTypeName: '{{pascalCase}}StateQuery',
11
11
  },
12
+ 'bootstrap-query': {
13
+ sourceTypeName: '{{pascalCase}}BootstrapQuery',
14
+ },
12
15
  'write-state-request': {
13
16
  sourceTypeName: '{{pascalCase}}WriteStateRequest',
14
17
  },
18
+ 'bootstrap-response': {
19
+ sourceTypeName: '{{pascalCase}}BootstrapResponse',
20
+ },
15
21
  'state-response': {
16
22
  sourceTypeName: '{{pascalCase}}StateResponse',
17
23
  },
@@ -40,6 +46,16 @@ export const BLOCKS = [
40
46
  mechanism: '{{restWriteAuthMechanism}}',
41
47
  },
42
48
  },
49
+ {
50
+ auth: 'public',
51
+ method: 'GET',
52
+ operationId: 'get{{pascalCase}}Bootstrap',
53
+ path: '/{{namespace}}/v1/{{slugKebabCase}}/bootstrap',
54
+ queryContract: 'bootstrap-query',
55
+ responseContract: 'bootstrap-response',
56
+ summary: 'Read fresh session bootstrap state for the current viewer.',
57
+ tags: [ '{{title}}' ],
58
+ },
43
59
  ],
44
60
  info: {
45
61
  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
  }