@ampless/backend 1.0.0-alpha.71 → 1.0.0-beta.73

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.
package/README.ja.md CHANGED
@@ -5,14 +5,14 @@
5
5
 
6
6
  [ampless](https://github.com/heavymoons/ampless) 向け Amplify Gen 2 バックエンドファクトリー。IAM / SQS / DynamoDB ストリームの配線、認証 / データ / ストレージの定義、およびすべてのイベント処理 Lambda を `defineAmplessBackend(...)` ひとつのファクトリーにまとめます。
7
7
 
8
- > **プレリリース / アルファ版。** v1.0 まではマイナーバージョンでも破壊的変更が入る可能性があります。
8
+ > **プレリリース / ベータ版。** v1.0 まではマイナーバージョンでも破壊的変更が入る可能性があります。
9
9
 
10
10
  テンプレートから切り出すことで、スキャフォールダーを再実行せずに `npm update @ampless/backend` でアップデートできます。バックエンドのバグ修正やインフラ改善はパッケージ経由で届きます。ユーザー側の `amplify/` ツリーは、ファクトリーを組み合わせる 1〜5 行のシェルだけで済みます。
11
11
 
12
12
  ## インストール
13
13
 
14
14
  ```bash
15
- npm install @ampless/backend@alpha ampless@alpha
15
+ npm install @ampless/backend@beta ampless@beta
16
16
  ```
17
17
 
18
18
  ピア依存: `@aws-amplify/backend`(^1)、`aws-cdk-lib`(^2)。CLI スキャフォールダーがテンプレートの `package.json` に互換バージョンをピン留めします。
package/README.md CHANGED
@@ -5,14 +5,14 @@
5
5
 
6
6
  Amplify Gen 2 backend factories for [ampless](https://github.com/heavymoons/ampless). Bundles the IAM / SQS / DynamoDB-stream wiring, the auth / data / storage definitions, and every event-processing Lambda behind one `defineAmplessBackend(...)` factory.
7
7
 
8
- > **Pre-release / alpha.** Breaking changes possible in any minor version until v1.0.
8
+ > **Pre-release / beta.** Breaking changes possible in any minor version until v1.0.
9
9
 
10
10
  Splitting this out of the template lets you `npm update @ampless/backend` without re-running the scaffolder. Backend bug fixes and infrastructure improvements arrive through the package; the user-side `amplify/` tree becomes a handful of 1–5 line shells that compose the factories.
11
11
 
12
12
  ## Install
13
13
 
14
14
  ```bash
15
- npm install @ampless/backend@alpha ampless@alpha
15
+ npm install @ampless/backend@beta ampless@beta
16
16
  ```
17
17
 
18
18
  Peer dependencies: `@aws-amplify/backend` (^1), `aws-cdk-lib` (^2). The CLI scaffolder pins compatible versions in the template's `package.json`.
@@ -222,7 +222,7 @@ function createProcessorTrustedHandler(opts) {
222
222
  }
223
223
  } else {
224
224
  console.warn(
225
- `[trusted-processor] ${label}: ctx.secret("${key}") \u2014 no encryption key found; returning undefined. Run \`npx create-ampless setup-encryption-key\` and rotate secrets.`
225
+ `[trusted-processor] ${label}: ctx.secret("${key}") \u2014 no encryption key found; returning undefined. Run \`npx create-ampless@beta setup-encryption-key\` and rotate secrets.`
226
226
  );
227
227
  secretCache.set(cacheKey, void 0);
228
228
  return void 0;
@@ -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
  },
@@ -18,13 +18,13 @@ function requireEnv(name) {
18
18
  var _encKeyB64 = process.env.PLUGIN_SECRET_ENCRYPTION_KEY;
19
19
  if (!_encKeyB64) {
20
20
  throw new Error(
21
- "[plugin-secret-handler] PLUGIN_SECRET_ENCRYPTION_KEY env var is not set. Run `npx create-ampless setup-encryption-key` and configure pluginSecretEncryptionKey in defineAmplessBackend()."
21
+ "[plugin-secret-handler] PLUGIN_SECRET_ENCRYPTION_KEY env var is not set. Run `npx create-ampless@beta setup-encryption-key` and configure pluginSecretEncryptionKey in defineAmplessBackend()."
22
22
  );
23
23
  }
24
24
  var _encKeyBuf = Buffer.from(_encKeyB64, "base64");
25
25
  if (_encKeyBuf.byteLength !== 32) {
26
26
  throw new Error(
27
- `[plugin-secret-handler] PLUGIN_SECRET_ENCRYPTION_KEY must encode 32 bytes (got ${_encKeyBuf.byteLength}). Re-run \`npx create-ampless setup-encryption-key\`.`
27
+ `[plugin-secret-handler] PLUGIN_SECRET_ENCRYPTION_KEY must encode 32 bytes (got ${_encKeyBuf.byteLength}). Re-run \`npx create-ampless@beta setup-encryption-key\`.`
28
28
  );
29
29
  }
30
30
  var ENCRYPTION_KEY = _encKeyBuf;
package/dist/index.d.ts CHANGED
@@ -24,7 +24,7 @@ interface DefineAmplessBackendOpts {
24
24
  * and writes only ciphertext to DDB. The admin browser never touches
25
25
  * PluginSecret directly.
26
26
  *
27
- * Generate and commit the key with `npx create-ampless setup-encryption-key`,
27
+ * Generate and commit the key with `npx create-ampless@beta setup-encryption-key`,
28
28
  * then pass it via `pluginSecretEncryptionKey` below. No AWS credentials
29
29
  * required for key provisioning.
30
30
  *
@@ -37,7 +37,7 @@ interface DefineAmplessBackendOpts {
37
37
  * AES-256-GCM encryption key for plugin secret storage (v2.2).
38
38
  *
39
39
  * A base64-encoded 32-byte key generated by
40
- * `npx create-ampless setup-encryption-key` and stored in
40
+ * `npx create-ampless@beta setup-encryption-key` and stored in
41
41
  * `amplify/secrets/encryption-key.ts` (adjacent to `amplify/backend.ts`).
42
42
  *
43
43
  * Example wiring in `amplify/backend.ts`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ampless/backend",
3
- "version": "1.0.0-alpha.71",
3
+ "version": "1.0.0-beta.73",
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.55",
73
- "ampless": "1.0.0-alpha.49"
72
+ "@ampless/mcp-server": "1.0.0-beta.57",
73
+ "ampless": "1.0.0-beta.51"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "@aws-amplify/backend": "^1",