@ampless/backend 1.0.0-alpha.70 → 1.0.0-alpha.72

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.
@@ -4,6 +4,7 @@ import { DynamoDBDocumentClient, GetCommand, UpdateCommand } from "@aws-sdk/lib-
4
4
  import { createHash } from "crypto";
5
5
 
6
6
  // ../mcp-server/dist/index.js
7
+ import { filterSortPostSummaries } from "ampless";
7
8
  import {
8
9
  decodeAwsJson
9
10
  } from "ampless";
@@ -53,11 +54,28 @@ var POST_FIELDS = (
53
54
  body
54
55
  status
55
56
  publishedAt
57
+ updatedAt
56
58
  tags
57
59
  metadata
58
60
  }
59
61
  `
60
62
  );
63
+ var POST_SUMMARY_FIELDS = (
64
+ /* GraphQL */
65
+ `
66
+ fragment PostSummaryFields on Post {
67
+ postId
68
+ slug
69
+ title
70
+ excerpt
71
+ format
72
+ status
73
+ publishedAt
74
+ updatedAt
75
+ tags
76
+ }
77
+ `
78
+ );
61
79
  function toCorePost(p) {
62
80
  const metadata = decodeAwsJson(p.metadata);
63
81
  return {
@@ -69,18 +87,32 @@ function toCorePost(p) {
69
87
  body: decodeAwsJson(p.body),
70
88
  status: p.status ?? "draft",
71
89
  publishedAt: p.publishedAt ?? void 0,
90
+ updatedAt: p.updatedAt ?? void 0,
72
91
  tags: (p.tags ?? []).filter((t) => typeof t === "string"),
73
92
  metadata: metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : void 0
74
93
  };
75
94
  }
95
+ function toPostSummary(p) {
96
+ return {
97
+ postId: p.postId,
98
+ slug: p.slug,
99
+ title: p.title,
100
+ excerpt: p.excerpt ?? void 0,
101
+ format: p.format ?? "markdown",
102
+ status: p.status ?? "draft",
103
+ publishedAt: p.publishedAt ?? void 0,
104
+ updatedAt: p.updatedAt ?? void 0,
105
+ tags: (p.tags ?? []).filter((t) => typeof t === "string")
106
+ };
107
+ }
76
108
  var QUERY = (
77
109
  /* GraphQL */
78
110
  `
79
- ${POST_FIELDS}
111
+ ${POST_SUMMARY_FIELDS}
80
112
  query ListPosts($filter: ModelPostFilterInput, $limit: Int, $nextToken: String) {
81
113
  listPosts(filter: $filter, limit: $limit, nextToken: $nextToken) {
82
114
  items {
83
- ...PostFields
115
+ ...PostSummaryFields
84
116
  }
85
117
  nextToken
86
118
  }
@@ -90,29 +122,95 @@ var QUERY = (
90
122
  var listPostsSchema = {
91
123
  type: "object",
92
124
  properties: {
125
+ query: {
126
+ type: "string",
127
+ description: "Case-insensitive substring match over title / slug / tags"
128
+ },
129
+ tag: {
130
+ type: "string",
131
+ description: "Exact tag filter"
132
+ },
93
133
  status: {
94
134
  type: "string",
95
135
  enum: ["draft", "published", "all"],
96
136
  description: 'Filter by status (default "all")'
97
137
  },
98
- limit: { type: "integer", minimum: 1, maximum: 100, description: "Max results (default 20)" },
99
- nextToken: { type: "string", description: "Pagination cursor from a previous call" }
138
+ sort: {
139
+ type: "string",
140
+ enum: [
141
+ "updated-desc",
142
+ "updated-asc",
143
+ "published-desc",
144
+ "published-asc",
145
+ "title-asc",
146
+ "title-desc"
147
+ ],
148
+ description: 'Sort order (default "updated-desc" \u2014 most recently edited first)'
149
+ },
150
+ limit: {
151
+ type: "integer",
152
+ minimum: 1,
153
+ maximum: 100,
154
+ description: "Max results per page (default 20)"
155
+ },
156
+ offset: {
157
+ type: "integer",
158
+ minimum: 0,
159
+ description: "Zero-based offset into the filtered result set (default 0)"
160
+ }
100
161
  }
101
162
  };
163
+ var VALID_STATUS = /* @__PURE__ */ new Set(["draft", "published", "all"]);
164
+ var VALID_SORT = /* @__PURE__ */ new Set([
165
+ "updated-desc",
166
+ "updated-asc",
167
+ "published-desc",
168
+ "published-asc",
169
+ "title-asc",
170
+ "title-desc"
171
+ ]);
172
+ function asString(v) {
173
+ return typeof v === "string" ? v : void 0;
174
+ }
175
+ function asInt(v, fallback) {
176
+ const n = Number(v);
177
+ return Number.isFinite(n) ? Math.trunc(n) : fallback;
178
+ }
102
179
  async function listPosts(client, args = {}) {
103
- const status = args.status ?? "all";
180
+ const limit = Math.min(100, Math.max(1, asInt(args.limit, 20)));
181
+ const offset = Math.max(0, asInt(args.offset, 0));
182
+ const rawStatus = asString(args.status);
183
+ const status = rawStatus && VALID_STATUS.has(rawStatus) ? rawStatus : "all";
184
+ const rawSort = asString(args.sort);
185
+ const sort = rawSort && VALID_SORT.has(rawSort) ? rawSort : void 0;
186
+ const query = asString(args.query);
187
+ const tag = asString(args.tag);
104
188
  const filter = {};
105
189
  if (status !== "all") filter.status = { eq: status };
106
190
  const hasFilter = Object.keys(filter).length > 0;
107
- const data = await client.query(QUERY, {
108
- filter: hasFilter ? filter : void 0,
109
- limit: args.limit ?? 20,
110
- nextToken: args.nextToken
191
+ const allItems = [];
192
+ let cursor = null;
193
+ do {
194
+ const data = await client.query(QUERY, {
195
+ filter: hasFilter ? filter : void 0,
196
+ limit: 200,
197
+ nextToken: cursor
198
+ });
199
+ for (const item of data.listPosts.items) {
200
+ allItems.push(toPostSummary(item));
201
+ }
202
+ cursor = data.listPosts.nextToken;
203
+ } while (cursor);
204
+ const filtered = filterSortPostSummaries(allItems, {
205
+ query,
206
+ tag,
207
+ sort,
208
+ // status already applied server-side; pass 'all' to avoid double-filter
209
+ status: "all"
111
210
  });
112
- return {
113
- posts: data.listPosts.items.map(toCorePost),
114
- nextToken: data.listPosts.nextToken
115
- };
211
+ const total = filtered.length;
212
+ const posts = filtered.slice(offset, offset + limit);
213
+ return { posts, total, offset, limit };
116
214
  }
117
215
  var GET_BY_ID = (
118
216
  /* GraphQL */
@@ -1547,7 +1645,7 @@ async function commitStaticPost(graphql, storage, args) {
1547
1645
  var tools = [
1548
1646
  {
1549
1647
  name: "list_posts",
1550
- description: "List posts in the CMS with optional filters by status. Returns up to `limit` posts (default 20) plus a `nextToken` cursor for pagination.",
1648
+ description: "Returns lightweight post summaries (no body \u2014 use get_post for content) with search / sort / filters. `total` reflects the filtered count. Supports query (substring over title/slug/tags), tag (exact), status, sort, limit (1\u2013100, default 20), offset (default 0).",
1551
1649
  inputSchema: listPostsSchema,
1552
1650
  handler: (args, ctx) => listPosts(ctx.graphql, args)
1553
1651
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ampless/backend",
3
- "version": "1.0.0-alpha.70",
3
+ "version": "1.0.0-alpha.72",
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",
@@ -69,8 +69,8 @@
69
69
  "@smithy/protocol-http": "^5.4.4",
70
70
  "@smithy/signature-v4": "^5.4.4",
71
71
  "fflate": "^0.8.3",
72
- "@ampless/mcp-server": "1.0.0-alpha.54",
73
- "ampless": "1.0.0-alpha.48"
72
+ "@ampless/mcp-server": "1.0.0-alpha.56",
73
+ "ampless": "1.0.0-alpha.50"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "@aws-amplify/backend": "^1",