uniweb 0.69.4 → 0.69.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.69.4",
3
+ "version": "0.69.5",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,17 +41,17 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
- "@uniweb/runtime": "^0.27.4",
45
- "@uniweb/schemas": "^0.7.0",
46
44
  "@uniweb/content-writer": "^0.3.4",
47
45
  "@uniweb/core": "^0.29.8",
48
46
  "@uniweb/kit": "^0.19.4",
47
+ "@uniweb/schemas": "^0.7.0",
48
+ "@uniweb/runtime": "^0.27.4",
49
49
  "@uniweb/semantic-parser": "^1.4.1"
50
50
  },
51
51
  "peerDependencies": {
52
+ "@uniweb/build": "^0.63.4",
52
53
  "@uniweb/content-reader": "^1.2.5",
53
- "@uniweb/semantic-parser": "^1.4.1",
54
- "@uniweb/build": "^0.63.3"
54
+ "@uniweb/semantic-parser": "^1.4.1"
55
55
  },
56
56
  "peerDependenciesMeta": {
57
57
  "@uniweb/build": {
@@ -45,7 +45,8 @@ import {
45
45
  removeYamlScalar,
46
46
  harvestRecordItems,
47
47
  storedRecordItems,
48
- reprintRecordItems
48
+ reprintRecordItems,
49
+ matchStoredRecords
49
50
  } from '@uniweb/build/uwx'
50
51
 
51
52
  // First entity `$`-document out of a `.uwx` we produced or the backend served.
@@ -878,6 +879,64 @@ export async function recoverRecordItemUuids({ client, siteDir, uuids, note }) {
878
879
  }
879
880
  }
880
881
 
882
+ /**
883
+ * Recover the records lane's identity this copy lacks for the site it is bound to — which stored
884
+ * record each of its records is (`records`), each placement's in the folder (`folders`), and the
885
+ * list items of the records it matches (`recordItems`) — from one read of the backend's folder
886
+ * lane. What the copy already holds is kept.
887
+ *
888
+ * ⛔ Until 2026-09-25 nothing recovered these, and a copy whose `sync.json` had lost them — a
889
+ * blog site whose push was refused after its content landed, with an older CLI — was refused on
890
+ * every push after: its folder sent three placements with no `$uuid` while three were stored
891
+ * ("one section carries no item identity (section contents)"). Unrefused, its records would have
892
+ * gone up with none, and the backend would have made a second entity of each.
893
+ *
894
+ * Reads only when the package names a record the map does not, or the folder map is empty; a
895
+ * record new to the backend costs that read and finds nothing (`matchStoredRecords`).
896
+ *
897
+ * @param {object} params
898
+ * @param {object[]} params.index - the records the package sends (`pkg.records.index`)
899
+ * @returns {Promise<number>} how many records and placements were recovered
900
+ */
901
+ export async function recoverRecordsIdentity({ client, siteDir, index, note }) {
902
+ const state = readBackendState(siteDir, client.origin)
903
+ const siteUuid = state.site?.uuid || null
904
+ const recordMap = state.records || {}
905
+ const records = (index || []).filter((e) => e?.ownId && e.model && e.slug)
906
+ const folders = readFolderItemUuids(siteDir, client.origin)
907
+ const unmapped = records.some((e) => !recordMap[e.ownId])
908
+ if (!siteUuid || !records.length || (!unmapped && Object.keys(folders).length)) return 0
909
+ try {
910
+ const res = await client.pullFolder(siteUuid)
911
+ if (!res?.ok) return 0
912
+ const docs = entityDocsFromUwx(Buffer.from(await res.arrayBuffer()))
913
+ const folderDoc = docs.find((d) => d?.$schema === '@uniweb/folder')
914
+ if (!folderDoc) return 0
915
+ const learned = matchStoredRecords({ index: records, recordMap, folderDoc })
916
+ const matched = Object.keys(learned).length
917
+ if (matched) {
918
+ updateBackendMap(siteDir, client.origin, 'records', learned)
919
+ const stored = new Map(docs.map((d) => [d?.$uuid, d]))
920
+ const items = {}
921
+ for (const theirs of Object.values(learned)) {
922
+ const doc = stored.get(theirs)
923
+ if (doc) items[theirs] = storedRecordItems(doc)
924
+ }
925
+ writeRecordItemUuids(siteDir, client.origin, items)
926
+ }
927
+ const placements = collectFolderItemUuids(folderDoc)
928
+ const missing = Object.fromEntries(Object.entries(placements).filter(([k]) => !folders[k]))
929
+ writeFolderItemUuids(siteDir, client.origin, missing)
930
+ const placed = Object.keys(missing).length
931
+ if (matched || placed) {
932
+ note?.(`Recovered the identity of ${matched} record(s) and ${placed} folder placement(s) from the backend.`)
933
+ }
934
+ return matched + placed
935
+ } catch {
936
+ return 0
937
+ }
938
+ }
939
+
881
940
  /**
882
941
  * The workspace this site was created in on `backend` — its owner, as the create
883
942
  * answered it — in the header's form: `@handle`, a unit's bare uuid when that unit has
@@ -1263,8 +1322,9 @@ export async function recoverItemUuids({ client, siteDir, note }) {
1263
1322
 
1264
1323
  /**
1265
1324
  * Recover the identity a package just built went out without, when the backend may hold it —
1266
- * a page or section (`itemIdentity.unknown`), or a record's list items
1267
- * (`recordItemIdentity.unbanked`) — and say what to build the package again with.
1325
+ * a page or section (`itemIdentity.unknown`), a record or a folder placement
1326
+ * (`recoverRecordsIdentity`), or a record's list items (`recordItemIdentity.unbanked`) — and say
1327
+ * what to build the package again with.
1268
1328
  *
1269
1329
  * A page added since the last push is also without identity, and costs one read that finds
1270
1330
  * nothing. That is the price of never sending as new an item the backend already holds.
@@ -1277,6 +1337,15 @@ export async function recoverUnbankedIdentity({ client, siteDir, pkg, note }) {
1277
1337
  if (pkg?.itemIdentity?.unknown > 0 && (await recoverItemUuids({ client, siteDir, note }))) {
1278
1338
  again = { ...again, itemUuids: readItemUuids(siteDir, client.origin) }
1279
1339
  }
1340
+ // The records the package sends, and the folder placing them. The emit reads the `records`
1341
+ // map from sync.json itself, so it needs no option — only the build again.
1342
+ if (await recoverRecordsIdentity({ client, siteDir, index: pkg?.records?.index, note })) {
1343
+ again = {
1344
+ ...again,
1345
+ folderItemUuids: readFolderItemUuids(siteDir, client.origin),
1346
+ recordItemUuids: readRecordItemUuids(siteDir, client.origin)
1347
+ }
1348
+ }
1280
1349
  const unbanked = pkg?.recordItemIdentity?.unbanked
1281
1350
  if (unbanked?.length && (await recoverRecordItemUuids({ client, siteDir, uuids: unbanked, note }))) {
1282
1351
  again = { ...again, recordItemUuids: readRecordItemUuids(siteDir, client.origin) }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-09-25T16:13:29.075Z",
3
+ "generatedAt": "2026-09-25T17:28:23.816Z",
4
4
  "packages": {
5
5
  "@uniweb/api": {
6
6
  "version": "0.6.2",
@@ -10,7 +10,7 @@
10
10
  ]
11
11
  },
12
12
  "@uniweb/build": {
13
- "version": "0.63.3",
13
+ "version": "0.63.4",
14
14
  "path": "framework/build",
15
15
  "deps": [
16
16
  "@uniweb/content-reader",