@10x-media/form-builder 0.1.0-beta.12 → 0.1.0-beta.13

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @10x-media/form-builder
2
2
 
3
+ ## 0.1.0-beta.13
4
+
5
+ ### Patch Changes
6
+
7
+ - Make the poll tally writer's Postgres import opaque to bundlers.
8
+
9
+ The optional `@payloadcms/db-postgres` peer was loaded via a literal dynamic import, which bundlers resolve at build time: a Mongo host on Turbopack failed `next build` with "Module not found" even though the Postgres branch never runs there (and `serverExternalPackages` does not help, since it governs bundling, not resolution). The specifier is now built at runtime, so no bundler resolves it and the package is only touched when the adapter is actually Postgres. Mongo hosts that worked around this with a resolve alias or stub can remove it.
10
+
11
+ Two upgrade notes from the same integrator round: hosts that had extended the select field block with their own `display` field must remove it on the beta.12 bump (the plugin ships one with identical values, and Payload refuses to boot with two same-name fields); and custom renderers replace the built-ins wholesale, so per-instance settings added in minor releases (`display` variants, `autocomplete`, calculation formatting) must be read by your renderer or an author's choice is silently ignored, now documented in the rendering guide.
12
+
3
13
  ## 0.1.0-beta.12
4
14
 
5
15
  ### Minor Changes
@@ -2,6 +2,22 @@ import { POLL_VOTES_SLUG } from "./votesCollection.js";
2
2
  //#region src/poll/votes/bumpPollVote.ts
3
3
  const PG_TABLE_KEY = "form_poll_votes";
4
4
  /**
5
+ * Loads the optional Postgres peer without a literal specifier: bundlers (Turbopack, webpack,
6
+ * Vite) resolve literal dynamic imports at build time, which fails a Mongo host's build even
7
+ * though this branch is unreachable there. The function-built specifier is opaque to their
8
+ * analyzers, the ignore comments cover bundlers that would still warn, and the type-only
9
+ * reference above erases at compile time, so the package is touched only when the Postgres
10
+ * branch actually runs.
11
+ */
12
+ const importPostgresSql = () => {
13
+ return import(
14
+ /* webpackIgnore: true */
15
+ /* turbopackIgnore: true */
16
+ /* @vite-ignore */
17
+ ["@payloadcms", "db-postgres"].join("/")
18
+ );
19
+ };
20
+ /**
5
21
  * Atomically bumps one (form, field, value) tally by `by` in a single upsert-increment
6
22
  * statement (Mongo `$inc` + upsert, Postgres `INSERT ... ON CONFLICT DO UPDATE`); the unique
7
23
  * compound index makes concurrent bumps for the same key safe without read-modify-write races.
@@ -46,7 +62,7 @@ async function bumpPollVote(payload, key, by, transactionID) {
46
62
  }
47
63
  return;
48
64
  }
49
- const { sql } = await import("@payloadcms/db-postgres");
65
+ const { sql } = await importPostgresSql();
50
66
  const db = payload.db;
51
67
  const tableName = db.tableNameMap.get(PG_TABLE_KEY);
52
68
  if (!tableName) throw new Error(`form-builder: drizzle table "${PG_TABLE_KEY}" not found`);
@@ -1 +1 @@
1
- {"version":3,"file":"bumpPollVote.js","names":[],"sources":["../../../src/poll/votes/bumpPollVote.ts"],"sourcesContent":["import type { Payload } from 'payload'\nimport { POLL_VOTES_SLUG, VOTE_SHARDS } from './votesCollection'\n\nconst PG_TABLE_KEY = 'form_poll_votes'\n\n// payload.db raw-access shapes are intentionally loose; these narrow casts reach the\n// Mongoose driver collection / Drizzle instance for atomic upserts (no public typed API).\ntype MongoDb = {\n\tname: 'mongoose'\n\tcollections: Record<\n\t\tstring,\n\t\t{ collection: { updateOne: (f: object, u: object, o: object) => Promise<unknown> } }\n\t>\n\tsessions?: Record<number | string, unknown>\n}\ntype PgInsert = {\n\tinsert: (t: unknown) => {\n\t\tvalues: (v: unknown) => { onConflictDoUpdate: (c: unknown) => Promise<unknown> }\n\t}\n}\ntype PgDb = {\n\tname: 'postgres'\n\tdrizzle: PgInsert\n\tsessions?: Record<number | string, { db: PgInsert }>\n\ttables: Record<string, Record<string, unknown>>\n\ttableNameMap: Map<string, string>\n}\n\n/**\n * Atomically bumps one (form, field, value) tally by `by` in a single upsert-increment\n * statement (Mongo `$inc` + upsert, Postgres `INSERT ... ON CONFLICT DO UPDATE`); the unique\n * compound index makes concurrent bumps for the same key safe without read-modify-write races.\n * The shard column is internal: transactional Mongo bumps pick a random shard in\n * [0, VOTE_SHARDS) so concurrent transactions rarely write the same document (see VOTE_SHARDS);\n * Postgres and non-transactional Mongo always bump shard 0. Readers sum across shards.\n *\n * When `transactionID` names an open Payload transaction, the write joins it: a bump failure\n * thrown from the submission hook rolls back the submission create (no undercount), and an\n * aborted create rolls back the joined bump (no overcount). Residual risk on transactional\n * Mongo: two concurrent bumps that land on the same shard still abort one transaction\n * (WriteConflict, labelled TransientTransactionError); the losing submission rolls back whole,\n * so counts stay consistent and the client can safely resubmit. Without a transaction (e.g.\n * Mongo with transactions disabled) the write lands on the root handle immediately; a later\n * recount from stored submissions is the healer for any drift that window allows.\n */\n// biome-ignore lint/complexity/useMaxParams: write primitive signature (payload, key, by, transactionID)\nexport async function bumpPollVote(\n\tpayload: Payload,\n\tkey: { form: string; field: string; value: string },\n\tby: number,\n\ttransactionID?: number | string\n): Promise<void> {\n\tif (payload.db.name === 'mongoose') {\n\t\tconst db = payload.db as unknown as MongoDb\n\t\tconst model = db.collections[POLL_VOTES_SLUG]\n\t\tif (!model) throw new Error(`form-builder: mongoose collection \"${POLL_VOTES_SLUG}\" not found`)\n\t\tconst session = transactionID !== undefined ? db.sessions?.[transactionID] : undefined\n\t\tconst shard = session ? Math.floor(Math.random() * VOTE_SHARDS) : 0\n\t\tconst shardedKey = { ...key, shard }\n\t\tconst update = { $inc: { count: by }, $setOnInsert: shardedKey }\n\t\tconst options = session ? { upsert: true, session } : { upsert: true }\n\t\ttry {\n\t\t\tawait model.collection.updateOne(shardedKey, update, options)\n\t\t} catch (error) {\n\t\t\t// Concurrent first inserts for a new key can race the upsert into E11000; outside a\n\t\t\t// transaction the row now exists, so one retry takes the $inc branch. Inside a\n\t\t\t// transaction the error propagates and Payload's rollback/retry semantics apply.\n\t\t\tconst duplicate = (error as { code?: unknown } | null)?.code === 11000\n\t\t\tif (session || !duplicate) throw error\n\t\t\tawait model.collection.updateOne(shardedKey, update, options)\n\t\t}\n\t\treturn\n\t}\n\tconst { sql } = await import('@payloadcms/db-postgres')\n\tconst db = payload.db as unknown as PgDb\n\tconst tableName = db.tableNameMap.get(PG_TABLE_KEY)\n\tif (!tableName) throw new Error(`form-builder: drizzle table \"${PG_TABLE_KEY}\" not found`)\n\tconst table = db.tables[tableName]\n\tif (!table) throw new Error(`form-builder: drizzle table object for \"${tableName}\" not found`)\n\tconst txn = transactionID !== undefined ? db.sessions?.[transactionID]?.db : undefined\n\tawait (txn ?? db.drizzle)\n\t\t.insert(table)\n\t\t.values({ ...key, shard: 0, count: by })\n\t\t.onConflictDoUpdate({\n\t\t\ttarget: [table.form, table.field, table.value, table.shard],\n\t\t\tset: { count: sql`${table.count} + ${by}` },\n\t\t})\n}\n"],"mappings":";;AAGA,MAAM,eAAe;;;;;;;;;;;;;;;;;;AA2CrB,eAAsB,aACrB,SACA,KACA,IACA,eACgB;CAChB,IAAI,QAAQ,GAAG,SAAS,YAAY;EACnC,MAAM,KAAK,QAAQ;EACnB,MAAM,QAAQ,GAAG,YAAY;EAC7B,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,sCAAsC,gBAAgB,YAAY;EAC9F,MAAM,UAAU,kBAAkB,KAAA,IAAY,GAAG,WAAW,iBAAiB,KAAA;EAC7E,MAAM,QAAQ,UAAU,KAAK,MAAM,KAAK,OAAO,IAAA,CAAe,IAAI;EAClE,MAAM,aAAa;GAAE,GAAG;GAAK;EAAM;EACnC,MAAM,SAAS;GAAE,MAAM,EAAE,OAAO,GAAG;GAAG,cAAc;EAAW;EAC/D,MAAM,UAAU,UAAU;GAAE,QAAQ;GAAM;EAAQ,IAAI,EAAE,QAAQ,KAAK;EACrE,IAAI;GACH,MAAM,MAAM,WAAW,UAAU,YAAY,QAAQ,OAAO;EAC7D,SAAS,OAAO;GAIf,MAAM,YAAa,OAAqC,SAAS;GACjE,IAAI,WAAW,CAAC,WAAW,MAAM;GACjC,MAAM,MAAM,WAAW,UAAU,YAAY,QAAQ,OAAO;EAC7D;EACA;CACD;CACA,MAAM,EAAE,QAAQ,MAAM,OAAO;CAC7B,MAAM,KAAK,QAAQ;CACnB,MAAM,YAAY,GAAG,aAAa,IAAI,YAAY;CAClD,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,gCAAgC,aAAa,YAAY;CACzF,MAAM,QAAQ,GAAG,OAAO;CACxB,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,2CAA2C,UAAU,YAAY;CAE7F,QADY,kBAAkB,KAAA,IAAY,GAAG,WAAW,gBAAgB,KAAK,KAAA,MAC/D,GAAG,SACf,OAAO,KAAK,EACZ,OAAO;EAAE,GAAG;EAAK,OAAO;EAAG,OAAO;CAAG,CAAC,EACtC,mBAAmB;EACnB,QAAQ;GAAC,MAAM;GAAM,MAAM;GAAO,MAAM;GAAO,MAAM;EAAK;EAC1D,KAAK,EAAE,OAAO,GAAG,GAAG,MAAM,MAAM,KAAK,KAAK;CAC3C,CAAC;AACH"}
1
+ {"version":3,"file":"bumpPollVote.js","names":[],"sources":["../../../src/poll/votes/bumpPollVote.ts"],"sourcesContent":["import type { Payload } from 'payload'\nimport { POLL_VOTES_SLUG, VOTE_SHARDS } from './votesCollection'\n\nconst PG_TABLE_KEY = 'form_poll_votes'\n\ntype PostgresSqlModule = { sql: typeof import('@payloadcms/db-postgres')['sql'] }\n\n/**\n * Loads the optional Postgres peer without a literal specifier: bundlers (Turbopack, webpack,\n * Vite) resolve literal dynamic imports at build time, which fails a Mongo host's build even\n * though this branch is unreachable there. The function-built specifier is opaque to their\n * analyzers, the ignore comments cover bundlers that would still warn, and the type-only\n * reference above erases at compile time, so the package is touched only when the Postgres\n * branch actually runs.\n */\nconst importPostgresSql = (): Promise<PostgresSqlModule> => {\n\tconst specifier = ['@payloadcms', 'db-postgres'].join('/')\n\treturn import(\n\t\t/* webpackIgnore: true */ /* turbopackIgnore: true */ /* @vite-ignore */ specifier\n\t) as Promise<PostgresSqlModule>\n}\n\n// payload.db raw-access shapes are intentionally loose; these narrow casts reach the\n// Mongoose driver collection / Drizzle instance for atomic upserts (no public typed API).\ntype MongoDb = {\n\tname: 'mongoose'\n\tcollections: Record<\n\t\tstring,\n\t\t{ collection: { updateOne: (f: object, u: object, o: object) => Promise<unknown> } }\n\t>\n\tsessions?: Record<number | string, unknown>\n}\ntype PgInsert = {\n\tinsert: (t: unknown) => {\n\t\tvalues: (v: unknown) => { onConflictDoUpdate: (c: unknown) => Promise<unknown> }\n\t}\n}\ntype PgDb = {\n\tname: 'postgres'\n\tdrizzle: PgInsert\n\tsessions?: Record<number | string, { db: PgInsert }>\n\ttables: Record<string, Record<string, unknown>>\n\ttableNameMap: Map<string, string>\n}\n\n/**\n * Atomically bumps one (form, field, value) tally by `by` in a single upsert-increment\n * statement (Mongo `$inc` + upsert, Postgres `INSERT ... ON CONFLICT DO UPDATE`); the unique\n * compound index makes concurrent bumps for the same key safe without read-modify-write races.\n * The shard column is internal: transactional Mongo bumps pick a random shard in\n * [0, VOTE_SHARDS) so concurrent transactions rarely write the same document (see VOTE_SHARDS);\n * Postgres and non-transactional Mongo always bump shard 0. Readers sum across shards.\n *\n * When `transactionID` names an open Payload transaction, the write joins it: a bump failure\n * thrown from the submission hook rolls back the submission create (no undercount), and an\n * aborted create rolls back the joined bump (no overcount). Residual risk on transactional\n * Mongo: two concurrent bumps that land on the same shard still abort one transaction\n * (WriteConflict, labelled TransientTransactionError); the losing submission rolls back whole,\n * so counts stay consistent and the client can safely resubmit. Without a transaction (e.g.\n * Mongo with transactions disabled) the write lands on the root handle immediately; a later\n * recount from stored submissions is the healer for any drift that window allows.\n */\n// biome-ignore lint/complexity/useMaxParams: write primitive signature (payload, key, by, transactionID)\nexport async function bumpPollVote(\n\tpayload: Payload,\n\tkey: { form: string; field: string; value: string },\n\tby: number,\n\ttransactionID?: number | string\n): Promise<void> {\n\tif (payload.db.name === 'mongoose') {\n\t\tconst db = payload.db as unknown as MongoDb\n\t\tconst model = db.collections[POLL_VOTES_SLUG]\n\t\tif (!model) throw new Error(`form-builder: mongoose collection \"${POLL_VOTES_SLUG}\" not found`)\n\t\tconst session = transactionID !== undefined ? db.sessions?.[transactionID] : undefined\n\t\tconst shard = session ? Math.floor(Math.random() * VOTE_SHARDS) : 0\n\t\tconst shardedKey = { ...key, shard }\n\t\tconst update = { $inc: { count: by }, $setOnInsert: shardedKey }\n\t\tconst options = session ? { upsert: true, session } : { upsert: true }\n\t\ttry {\n\t\t\tawait model.collection.updateOne(shardedKey, update, options)\n\t\t} catch (error) {\n\t\t\t// Concurrent first inserts for a new key can race the upsert into E11000; outside a\n\t\t\t// transaction the row now exists, so one retry takes the $inc branch. Inside a\n\t\t\t// transaction the error propagates and Payload's rollback/retry semantics apply.\n\t\t\tconst duplicate = (error as { code?: unknown } | null)?.code === 11000\n\t\t\tif (session || !duplicate) throw error\n\t\t\tawait model.collection.updateOne(shardedKey, update, options)\n\t\t}\n\t\treturn\n\t}\n\tconst { sql } = await importPostgresSql()\n\tconst db = payload.db as unknown as PgDb\n\tconst tableName = db.tableNameMap.get(PG_TABLE_KEY)\n\tif (!tableName) throw new Error(`form-builder: drizzle table \"${PG_TABLE_KEY}\" not found`)\n\tconst table = db.tables[tableName]\n\tif (!table) throw new Error(`form-builder: drizzle table object for \"${tableName}\" not found`)\n\tconst txn = transactionID !== undefined ? db.sessions?.[transactionID]?.db : undefined\n\tawait (txn ?? db.drizzle)\n\t\t.insert(table)\n\t\t.values({ ...key, shard: 0, count: by })\n\t\t.onConflictDoUpdate({\n\t\t\ttarget: [table.form, table.field, table.value, table.shard],\n\t\t\tset: { count: sql`${table.count} + ${by}` },\n\t\t})\n}\n"],"mappings":";;AAGA,MAAM,eAAe;;;;;;;;;AAYrB,MAAM,0BAAsD;CAE3D,OAAO;;;;EADW,CAAC,eAAe,aAAa,EAAE,KAAK,GAE4B;;AAEnF;;;;;;;;;;;;;;;;;;AA2CA,eAAsB,aACrB,SACA,KACA,IACA,eACgB;CAChB,IAAI,QAAQ,GAAG,SAAS,YAAY;EACnC,MAAM,KAAK,QAAQ;EACnB,MAAM,QAAQ,GAAG,YAAY;EAC7B,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,sCAAsC,gBAAgB,YAAY;EAC9F,MAAM,UAAU,kBAAkB,KAAA,IAAY,GAAG,WAAW,iBAAiB,KAAA;EAC7E,MAAM,QAAQ,UAAU,KAAK,MAAM,KAAK,OAAO,IAAA,CAAe,IAAI;EAClE,MAAM,aAAa;GAAE,GAAG;GAAK;EAAM;EACnC,MAAM,SAAS;GAAE,MAAM,EAAE,OAAO,GAAG;GAAG,cAAc;EAAW;EAC/D,MAAM,UAAU,UAAU;GAAE,QAAQ;GAAM;EAAQ,IAAI,EAAE,QAAQ,KAAK;EACrE,IAAI;GACH,MAAM,MAAM,WAAW,UAAU,YAAY,QAAQ,OAAO;EAC7D,SAAS,OAAO;GAIf,MAAM,YAAa,OAAqC,SAAS;GACjE,IAAI,WAAW,CAAC,WAAW,MAAM;GACjC,MAAM,MAAM,WAAW,UAAU,YAAY,QAAQ,OAAO;EAC7D;EACA;CACD;CACA,MAAM,EAAE,QAAQ,MAAM,kBAAkB;CACxC,MAAM,KAAK,QAAQ;CACnB,MAAM,YAAY,GAAG,aAAa,IAAI,YAAY;CAClD,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,gCAAgC,aAAa,YAAY;CACzF,MAAM,QAAQ,GAAG,OAAO;CACxB,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,2CAA2C,UAAU,YAAY;CAE7F,QADY,kBAAkB,KAAA,IAAY,GAAG,WAAW,gBAAgB,KAAK,KAAA,MAC/D,GAAG,SACf,OAAO,KAAK,EACZ,OAAO;EAAE,GAAG;EAAK,OAAO;EAAG,OAAO;CAAG,CAAC,EACtC,mBAAmB;EACnB,QAAQ;GAAC,MAAM;GAAM,MAAM;GAAO,MAAM;GAAO,MAAM;EAAK;EAC1D,KAAK,EAAE,OAAO,GAAG,GAAG,MAAM,MAAM,KAAK,KAAK;CAC3C,CAAC;AACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@10x-media/form-builder",
3
- "version": "0.1.0-beta.12",
3
+ "version": "0.1.0-beta.13",
4
4
  "description": "End-to-end forms platform for Payload: author, validate, render, collect, aggregate, and act.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -99,10 +99,10 @@
99
99
  "tsdown": "0.22.1",
100
100
  "typescript": "5.9.3",
101
101
  "vitest": "4.1.7",
102
- "@10x-media/tsconfig": "0.0.0",
102
+ "@10x-media/payload-test-harness": "0.0.0",
103
103
  "@10x-media/vitest-config": "0.0.0",
104
- "@10x-media/tsdown-config": "0.0.0",
105
- "@10x-media/payload-test-harness": "0.0.0"
104
+ "@10x-media/tsconfig": "0.0.0",
105
+ "@10x-media/tsdown-config": "0.0.0"
106
106
  },
107
107
  "publishConfig": {
108
108
  "access": "public"