@ampless/backend 1.0.0-alpha.19 → 1.0.0-alpha.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.
@@ -1,10 +1,10 @@
1
1
  /**
2
- * MCP HTTP endpoint Lambda. Phase 5: Bearer validation + JSON-RPC 2.0
3
- * tool dispatch over AppSync IAM auth, including `upload_media`.
2
+ * MCP HTTP endpoint Lambda. Bearer validation + JSON-RPC 2.0 tool
3
+ * dispatch over AppSync IAM auth, including `upload_media`.
4
4
  *
5
5
  * 1. Reads KvStore directly (PK = 'mcp-tokens', SK = SHA-256 hex)
6
- * to validate `Authorization: Bearer amk_...`. Same narrow IAM
7
- * grant as Phase 3 (`dynamodb:GetItem` on the KvStore table).
6
+ * to validate `Authorization: Bearer amk_...`. The Lambda has a
7
+ * narrow IAM grant: `dynamodb:GetItem` on the KvStore table.
8
8
  * 2. Parses the incoming JSON-RPC envelope by hand (no MCP SDK
9
9
  * stdio transport in a Lambda runtime — overkill for the three
10
10
  * verbs we actually need).
@@ -6,7 +6,7 @@ import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
6
6
  import { createHash } from "crypto";
7
7
  import { decodeAwsJson as decodeAwsJson2 } from "ampless";
8
8
 
9
- // ../mcp-server/dist/chunk-DAR6AHSX.js
9
+ // ../mcp-server/dist/chunk-K4GTND6P.js
10
10
  import {
11
11
  decodeAwsJson
12
12
  } from "ampless";
@@ -278,11 +278,17 @@ var createPostSchema = {
278
278
  tags: { type: "array", items: { type: "string" } },
279
279
  metadata: {
280
280
  type: "object",
281
- description: "Free-form per-post metadata. Reserved well-known keys: `no_layout` (boolean) \u2014 when true, the public page is served as bare HTML with no theme chrome (the route redirects to /_/<slug>); meaningful only with format=html. Other keys pass through unchanged for themes/plugins.",
281
+ description: "Free-form per-post metadata. Reserved well-known keys: `no_layout` (boolean) \u2014 when true, the public page is served as bare HTML with no theme chrome (middleware rewrites /<slug> to the internal bare-HTML route); meaningful only with format=html. `cache` (auto|deep|hot) \u2014 override the per-post cache strategy. See `cacheStrategy` in get_schema.notes for details. Other keys pass through unchanged for themes/plugins.",
282
282
  properties: {
283
283
  no_layout: {
284
284
  type: "boolean",
285
285
  description: "Serve the post as bare HTML with no theme chrome. Only meaningful when format=html; ignored otherwise."
286
+ },
287
+ cache: {
288
+ type: "string",
289
+ enum: ["auto", "deep", "hot"],
290
+ default: "auto",
291
+ description: "Override the response Cache-Control strategy. 'auto' (default): no-store within `cms.config.cache.cooldownMs` of updatedAt, then `max-age=freshTtlSeconds`. 'deep': always `max-age=deepTtlSeconds`. 'hot': always no-store. Independent of no_layout; applies uniformly to themed, no_layout, and static posts."
286
292
  }
287
293
  },
288
294
  additionalProperties: true
@@ -341,11 +347,17 @@ var updatePostSchema = {
341
347
  tags: { type: "array", items: { type: "string" } },
342
348
  metadata: {
343
349
  type: "object",
344
- description: "Free-form per-post metadata. Reserved well-known keys: `no_layout` (boolean) \u2014 when true, the public page is served as bare HTML with no theme chrome (the route redirects to /_/<slug>); meaningful only with format=html. Passing metadata replaces the existing metadata object \u2014 read the current post first if you only want to add a key.",
350
+ description: "Free-form per-post metadata. Reserved well-known keys: `no_layout` (boolean) \u2014 when true, the public page is served as bare HTML with no theme chrome (middleware rewrites /<slug> to the internal bare-HTML route); meaningful only with format=html. `cache` (auto|deep|hot) \u2014 override the per-post cache strategy. See `cacheStrategy` in get_schema.notes for details. Passing metadata replaces the existing metadata object \u2014 read the current post first if you only want to add a key.",
345
351
  properties: {
346
352
  no_layout: {
347
353
  type: "boolean",
348
354
  description: "Serve the post as bare HTML with no theme chrome. Only meaningful when format=html; ignored otherwise."
355
+ },
356
+ cache: {
357
+ type: "string",
358
+ enum: ["auto", "deep", "hot"],
359
+ default: "auto",
360
+ description: "Override the response Cache-Control strategy. 'auto' (default): no-store within `cms.config.cache.cooldownMs` of updatedAt, then `max-age=freshTtlSeconds`. 'deep': always `max-age=deepTtlSeconds`. 'hot': always no-store. Independent of no_layout; applies uniformly to themed, no_layout, and static posts."
349
361
  }
350
362
  },
351
363
  additionalProperties: true
@@ -473,7 +485,7 @@ function getSchema() {
473
485
  tags: { type: "string[]" },
474
486
  metadata: {
475
487
  type: "json",
476
- description: "Free-form per-post key/value bag. Reserved well-known keys (owned by ampless): `no_layout` (boolean). Other keys pass through unchanged for themes/plugins."
488
+ description: "Free-form per-post key/value bag. Reserved well-known keys (owned by ampless): `no_layout` (boolean), `cache` (auto|deep|hot). Other keys pass through unchanged for themes/plugins."
477
489
  }
478
490
  }
479
491
  },
@@ -506,8 +518,9 @@ function getSchema() {
506
518
  notes: {
507
519
  editorTrust: 'editor stores arbitrary HTML/JS verbatim \u2014 same trust shape as WordPress unfiltered_html capability. See docs/architecture/04-access-layer-mcp.md \xA7"editor \u306E\u4FE1\u983C\u30E2\u30C7\u30EB".',
508
520
  tiptapBody: 'When format=tiptap, body is the tiptap document JSON: { type: "doc", content: [...] }. The renderer expects this shape.',
509
- noLayout: "metadata.no_layout=true serves the post as bare HTML with no theme chrome \u2014 the public route at /<slug> 308-redirects to /_/<slug>, and that route renders the body verbatim with no wrapping <html>/<head>/layout. Use this for landing pages, embeds, or any post whose body is a full HTML document. Only meaningful with format=html (the other formats need the theme renderer).",
510
- staticFormat: "A fourth format value `static` exists on the underlying data model for posts whose body is a JSON manifest pointing to a pre-uploaded HTML/CSS/JS bundle in S3 at public/static/<slug>/. Public URL pattern: /_/<slug>/ for the entrypoint and /_/<slug>/<file> for every bundle file (308 redirect from /<slug> via the post dispatcher). Static posts are created/edited through the dedicated tools `upload_static_bundle` (zip in one shot), `upload_static_file` / `delete_static_file` (incremental per-file ops), and `commit_static_post` (rebuild the manifest from the current S3 prefix). `create_post` / `update_post` intentionally do NOT accept format=static \u2014 the bundle tools are the only supported entry point so the Post manifest stays in sync with the S3 prefix."
521
+ noLayout: "metadata.no_layout=true serves the post as bare HTML with no theme chrome \u2014 middleware rewrites the public /<slug> request to the internal bare-HTML handler, which renders the body verbatim with no wrapping <html>/<head>/layout. Use this for landing pages, embeds, or any post whose body is a full HTML document. Only meaningful with format=html (the other formats need the theme renderer).",
522
+ staticFormat: "A fourth format value `static` exists on the underlying data model for posts whose body is a JSON manifest pointing to a pre-uploaded HTML/CSS/JS bundle in S3 at public/static/<slug>/. Public URL pattern: /<slug>/ for the entrypoint and /<slug>/<file> for every bundle file (middleware rewrites both to an internal static-bundle handler). Static posts are created/edited through the dedicated tools `upload_static_bundle` (zip in one shot), `upload_static_file` / `delete_static_file` (incremental per-file ops), and `commit_static_post` (rebuild the manifest from the current S3 prefix). `create_post` / `update_post` intentionally do NOT accept format=static \u2014 the bundle tools are the only supported entry point so the Post manifest stays in sync with the S3 prefix.",
523
+ cacheStrategy: "metadata.cache controls the response Cache-Control header (set by middleware). Values:\n - 'auto' (default): cooldown by edit time. Posts updated within cms.config.cache.cooldownMs (default 1h) emit `public, max-age=0, must-revalidate, s-maxage=0` so editors see fresh content immediately; older posts emit `public, max-age=<freshTtlSeconds>, s-maxage=<freshTtlSeconds>` (default 300 sec / 5 min).\n - 'deep': always `public, max-age=<deepTtlSeconds>, s-maxage=<deepTtlSeconds>` (default 3600 sec / 1 hour). Use for posts whose content is fixed for the foreseeable future.\n - 'hot': always `public, max-age=0, must-revalidate, s-maxage=0`. Use for rapidly-evolving posts or posts whose body is computed per request.\nIndependent of metadata.no_layout and post.format \u2014 applies uniformly to themed, no_layout, and static posts. Override the defaults per project via `cms.config.cache` ({ cooldownMs, freshTtlSeconds, deepTtlSeconds })."
511
524
  }
512
525
  };
513
526
  }
@@ -834,7 +847,7 @@ var tools = [
834
847
  },
835
848
  {
836
849
  name: "create_post",
837
- description: "Create a new post. Title and slug are required. Body shape depends on format: tiptap=JSON node tree, markdown=source string, html=raw HTML string. Defaults to status=draft. Pass `metadata: { no_layout: true }` alongside format=html to publish the body as a bare HTML page with no theme chrome (the public route redirects to /_/<slug>).",
850
+ description: 'Create a new post. Title and slug are required. Body shape depends on format: tiptap=JSON node tree, markdown=source string, html=raw HTML string. Defaults to status=draft. Pass `metadata: { no_layout: true }` alongside format=html to publish the body as a bare HTML page with no theme chrome (middleware rewrites the /<slug> request to the internal bare-HTML handler). Pass `metadata: { cache: "deep" | "hot" }` to override the default cooldown-based cache strategy \u2014 see get_schema.notes.cacheStrategy for details.',
838
851
  inputSchema: createPostSchema,
839
852
  handler: (args, ctx) => createPost(ctx.graphql, args)
840
853
  },
package/dist/index.d.ts CHANGED
@@ -18,10 +18,8 @@ interface DefineAmplessBackendOpts {
18
18
  }
19
19
  type AmplessBackend = any;
20
20
  /**
21
- * The end-to-end ampless backend wiring: identical to the 251-line
22
- * `backend.ts` that used to live in `templates/_shared/amplify/`,
23
- * but parameterised on the resource objects so users only have to
24
- * compose the imports.
21
+ * The end-to-end ampless backend wiring, parameterised on the resource
22
+ * objects so users only have to compose the imports.
25
23
  *
26
24
  * What it does, in order:
27
25
  * 1. Calls `defineBackend` with every Ampless resource.
@@ -201,8 +199,8 @@ interface AmplessSchemaAuthorizationOpts {
201
199
  * destructure `resource` out of their `allow` parameter), so this
202
200
  * grant applies broadly — every model the Lambda calls is reachable.
203
201
  * That's wider than strictly necessary; the MCP tools' GraphQL
204
- * operations narrow the effective surface to Post / PostTag in
205
- * Phase 4 and Media in Phase 5.
202
+ * operations narrow the effective surface to Post / PostTag for
203
+ * content tools and Media for `upload_media`.
206
204
  *
207
205
  * Typed as `unknown` for the same reason `userAdminFunction` /
208
206
  * `mcpHandlerFunction` are in other helpers — `defineFunction`'s
package/dist/index.js CHANGED
@@ -364,6 +364,12 @@ function amplessSchemaModels(a, opts = {}) {
364
364
  ]),
365
365
  // Custom return type for public post reads. Decoupling from `Post` lets
366
366
  // AppSync skip the model-level (admin-only) auth check on fields.
367
+ //
368
+ // `updatedAt` is projected through so middleware can compute the
369
+ // `metadata.cache='auto'` cooldown without re-fetching the model
370
+ // row. It's an Amplify-managed DynamoDB attribute (set on every
371
+ // write); the JS resolvers pass items through verbatim so the
372
+ // value naturally appears here once the schema declares it.
367
373
  PublicPost: a.customType({
368
374
  postId: a.id().required(),
369
375
  slug: a.string().required(),
@@ -374,7 +380,8 @@ function amplessSchemaModels(a, opts = {}) {
374
380
  status: a.string(),
375
381
  publishedAt: a.datetime(),
376
382
  tags: a.string().array(),
377
- metadata: a.json()
383
+ metadata: a.json(),
384
+ updatedAt: a.datetime()
378
385
  }),
379
386
  // Paginated wrapper for list responses.
380
387
  PublicPostConnection: a.customType({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ampless/backend",
3
- "version": "1.0.0-alpha.19",
3
+ "version": "1.0.0-alpha.21",
4
4
  "description": "Amplify Gen 2 backend factories for ampless: auth, data, storage, event processors, API key renewer",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -65,8 +65,8 @@
65
65
  "@smithy/protocol-http": "^5.3.0",
66
66
  "@smithy/signature-v4": "^5.4.0",
67
67
  "fflate": "^0.8.2",
68
- "ampless": "1.0.0-alpha.9",
69
- "@ampless/mcp-server": "1.0.0-alpha.10"
68
+ "ampless": "1.0.0-alpha.11",
69
+ "@ampless/mcp-server": "1.0.0-alpha.12"
70
70
  },
71
71
  "peerDependencies": {
72
72
  "@aws-amplify/backend": "^1",