cvx-kit 0.1.13 → 0.1.14
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/AGENTS.md +47 -0
- package/CHANGELOG.md +20 -0
- package/README.md +144 -0
- package/dist/auth.d.mts +3 -1
- package/dist/auth.mjs +1 -1
- package/dist/auth.mjs.map +1 -1
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +1 -1
- package/dist/modules/effect/api-zod.d.mts +1 -1
- package/dist/oxlint/architecture.d.mts +2 -0
- package/dist/oxlint/architecture.mjs +1 -1
- package/dist/oxlint/architecture.mjs.map +1 -1
- package/dist/oxlint/ast.d.mts +10 -1
- package/dist/oxlint/ast.mjs +1 -1
- package/dist/oxlint/ast.mjs.map +1 -1
- package/dist/oxlint.d.mts +1 -1
- package/dist/oxlint.mjs +1 -1
- package/dist/oxlint.mjs.map +1 -1
- package/dist/querying/cursors.d.mts +45 -0
- package/dist/querying/cursors.mjs +2 -0
- package/dist/querying/cursors.mjs.map +1 -0
- package/dist/querying/execution.d.mts +30 -0
- package/dist/querying/execution.mjs +2 -0
- package/dist/querying/execution.mjs.map +1 -0
- package/dist/querying/filters.d.mts +136 -0
- package/dist/querying/filters.mjs +2 -0
- package/dist/querying/filters.mjs.map +1 -0
- package/dist/querying/planner.d.mts +50 -0
- package/dist/querying/planner.mjs +2 -0
- package/dist/querying/planner.mjs.map +1 -0
- package/dist/querying/relationships.d.mts +41 -0
- package/dist/querying/relationships.mjs +2 -0
- package/dist/querying/relationships.mjs.map +1 -0
- package/dist/querying/security.d.mts +107 -0
- package/dist/querying/security.mjs +2 -0
- package/dist/querying/security.mjs.map +1 -0
- package/dist/querying.d.mts +39 -0
- package/dist/querying.mjs +2 -0
- package/dist/querying.mjs.map +1 -0
- package/dist/relations-public.d.mts +2 -0
- package/dist/relations-public.mjs +1 -0
- package/dist/relations.d.mts +155 -0
- package/dist/relations.mjs +2 -0
- package/dist/relations.mjs.map +1 -0
- package/dist/zod-convex.d.mts +10 -0
- package/dist/zod-convex.mjs +2 -0
- package/dist/zod-convex.mjs.map +1 -0
- package/dist/zod-registration.d.mts +10 -0
- package/dist/zod-registration.mjs +2 -0
- package/dist/zod-registration.mjs.map +1 -0
- package/dist/zod-table.d.mts +63 -8
- package/dist/zod-table.mjs +1 -1
- package/dist/zod-table.mjs.map +1 -1
- package/docs/conventions.md +12 -0
- package/docs/llms.md +15 -5
- package/docs/oxlint.md +129 -25
- package/docs/querying.md +293 -0
- package/docs/zod-table.md +93 -13
- package/package.json +11 -1
- package/scripts/owned-process.mjs +140 -0
- package/scripts/owned-process.test.mjs +107 -0
- package/scripts/smoke-packed-querying.mjs +296 -0
- package/scripts/test-packed-effect.mjs +8 -1
- package/scripts/test-packed-oxlint.mjs +5 -1
- package/scripts/test-packed-querying.mjs +185 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { test } from 'vite-plus/test'
|
|
4
|
+
import { createServer } from 'node:net'
|
|
5
|
+
import { mkdtempSync, writeFileSync } from 'node:fs'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
import {
|
|
8
|
+
runOwnedProcess,
|
|
9
|
+
cleanupOwnedProcess,
|
|
10
|
+
processGroupAlive,
|
|
11
|
+
collectOwnedCleanup,
|
|
12
|
+
} from './owned-process.mjs'
|
|
13
|
+
|
|
14
|
+
const proof = mkdtempSync('/tmp/cvx-owned-process-')
|
|
15
|
+
console.log('Owned cleanup evidence: ' + proof)
|
|
16
|
+
|
|
17
|
+
const descendant = `process.on('SIGINT',()=>{}); process.on('SIGTERM',()=>{}); console.log('ready'); setInterval(()=>{},1000)`
|
|
18
|
+
const parent = `
|
|
19
|
+
const {spawn}=require('node:child_process');
|
|
20
|
+
const child=spawn(process.execPath,['-e',${JSON.stringify(descendant)}],{stdio:['ignore','pipe','ignore']});
|
|
21
|
+
process.on('SIGINT',()=>{}); process.on('SIGTERM',()=>{});
|
|
22
|
+
child.stdout.once('data',()=>{console.log('descendant:'+child.pid); MODE});
|
|
23
|
+
setInterval(()=>{},1000);
|
|
24
|
+
`
|
|
25
|
+
|
|
26
|
+
test('timeout terminates the entire owned group after ignored graceful signals', async () => {
|
|
27
|
+
let pid
|
|
28
|
+
try {
|
|
29
|
+
const result = await runOwnedProcess({
|
|
30
|
+
command: process.execPath,
|
|
31
|
+
args: ['-e', parent.replace('MODE', '')],
|
|
32
|
+
timeoutMs: 1500,
|
|
33
|
+
graceMs: 150,
|
|
34
|
+
onSpawn: (value) => {
|
|
35
|
+
pid = value
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
writeFileSync(join(proof, 'timeout.json'), JSON.stringify(result, null, 2))
|
|
39
|
+
assert.match(result.stdout, /descendant:\d+/)
|
|
40
|
+
assert.equal(result.timedOut, true)
|
|
41
|
+
assert.ok(result.timeoutCleanup.signals.includes('SIGKILL'))
|
|
42
|
+
assert.equal(result.cleanup.alive, false)
|
|
43
|
+
assert.equal(processGroupAlive(pid), false)
|
|
44
|
+
} finally {
|
|
45
|
+
if (pid) await cleanupOwnedProcess(pid, 150)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('failed leader preserves failure output and finally cleans its surviving descendant', async () => {
|
|
50
|
+
let pid
|
|
51
|
+
try {
|
|
52
|
+
const result = await runOwnedProcess({
|
|
53
|
+
command: process.execPath,
|
|
54
|
+
args: ['-e', parent.replace('MODE', "console.error('original failure'); process.exit(7)")],
|
|
55
|
+
timeoutMs: 1500,
|
|
56
|
+
graceMs: 150,
|
|
57
|
+
onSpawn: (value) => {
|
|
58
|
+
pid = value
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
writeFileSync(join(proof, 'failed-leader.json'), JSON.stringify(result, null, 2))
|
|
62
|
+
assert.equal(result.code, 7)
|
|
63
|
+
assert.equal(result.timedOut, false)
|
|
64
|
+
assert.match(result.stderr, /original failure/)
|
|
65
|
+
assert.equal(result.cleanup.alive, false)
|
|
66
|
+
assert.equal(processGroupAlive(pid), false)
|
|
67
|
+
} finally {
|
|
68
|
+
if (pid) await cleanupOwnedProcess(pid, 150)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('cleanup rejects a group that was not created by the runner', async () => {
|
|
73
|
+
await assert.rejects(cleanupOwnedProcess(process.pid, 20), /not owned/)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('port-check failure is recorded without replacing original proof failure', async () => {
|
|
77
|
+
const server = createServer()
|
|
78
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve))
|
|
79
|
+
const port = server.address().port
|
|
80
|
+
try {
|
|
81
|
+
const result = await runOwnedProcess({
|
|
82
|
+
command: process.execPath,
|
|
83
|
+
args: ['-e', 'process.exit(7)'],
|
|
84
|
+
timeoutMs: 1000,
|
|
85
|
+
})
|
|
86
|
+
const failure = new Error('original proof failure: exit ' + result.code)
|
|
87
|
+
const evidence = await collectOwnedCleanup({
|
|
88
|
+
pids: [result.pid],
|
|
89
|
+
ports: [port],
|
|
90
|
+
failure,
|
|
91
|
+
checkPort: (requested) =>
|
|
92
|
+
new Promise((resolve, reject) => {
|
|
93
|
+
const probe = createServer()
|
|
94
|
+
probe.once('error', reject)
|
|
95
|
+
probe.listen(requested, '127.0.0.1', () => probe.close(() => resolve(requested)))
|
|
96
|
+
}),
|
|
97
|
+
})
|
|
98
|
+
writeFileSync(join(proof, 'port-failure.json'), JSON.stringify(evidence, null, 2))
|
|
99
|
+
assert.equal(evidence.failure.message, 'original proof failure: exit 7')
|
|
100
|
+
assert.equal(evidence.ports[0].closed, false)
|
|
101
|
+
assert.equal(evidence.ports[0].error.code, 'EADDRINUSE')
|
|
102
|
+
assert.equal(evidence.groups[0].alive, false)
|
|
103
|
+
assert.equal(server.listening, true, 'unrelated listener must never be killed')
|
|
104
|
+
} finally {
|
|
105
|
+
await new Promise((resolve) => server.close(resolve))
|
|
106
|
+
}
|
|
107
|
+
})
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { execFileSync } from 'node:child_process'
|
|
3
|
+
import { runOwnedProcess, collectOwnedCleanup } from './owned-process.mjs'
|
|
4
|
+
import { createHash } from 'node:crypto'
|
|
5
|
+
import {
|
|
6
|
+
cpSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
mkdtempSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
symlinkSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from 'node:fs'
|
|
14
|
+
import { createServer } from 'node:net'
|
|
15
|
+
import { join, resolve, relative } from 'node:path'
|
|
16
|
+
import {
|
|
17
|
+
localConvexEnvironment,
|
|
18
|
+
assertIsolatedConvexFixture,
|
|
19
|
+
assertLocalConvexState,
|
|
20
|
+
} from './convex-smoke-env.mjs'
|
|
21
|
+
|
|
22
|
+
// Run against the retained installed peer of the exact packed stress candidate.
|
|
23
|
+
const packed = resolve(process.argv[2] ?? '')
|
|
24
|
+
const identity = JSON.parse(readFileSync(join(packed, 'package-identity.json'), 'utf8'))
|
|
25
|
+
const hash = (bytes) => createHash('sha256').update(bytes).digest('hex')
|
|
26
|
+
assert.equal(hash(readFileSync(identity.tarball)), identity.sha256)
|
|
27
|
+
const source = join(packed, 'installed')
|
|
28
|
+
assert.equal(
|
|
29
|
+
JSON.parse(readFileSync(join(source, 'node_modules/convex/package.json'), 'utf8')).version,
|
|
30
|
+
'1.46.0',
|
|
31
|
+
)
|
|
32
|
+
const fixture = mkdtempSync('/tmp/cvx-relational-engine-native-')
|
|
33
|
+
console.log(`U8 native retained proof: ${fixture}`)
|
|
34
|
+
cpSync(join(source, 'convex'), join(fixture, 'convex'), { recursive: true })
|
|
35
|
+
symlinkSync(join(source, 'node_modules'), join(fixture, 'node_modules'), 'dir')
|
|
36
|
+
writeFileSync(
|
|
37
|
+
join(fixture, 'package.json'),
|
|
38
|
+
JSON.stringify({
|
|
39
|
+
private: true,
|
|
40
|
+
type: 'module',
|
|
41
|
+
dependencies: { convex: '1.46.0', 'cvx-kit': identity.version },
|
|
42
|
+
}),
|
|
43
|
+
)
|
|
44
|
+
cpSync(join(source, 'register.mjs'), join(fixture, 'register.mjs'))
|
|
45
|
+
cpSync(
|
|
46
|
+
resolve(import.meta.dirname, '../test/fixture-packed-querying/audit-native.mjs.template'),
|
|
47
|
+
join(fixture, 'audit.mjs'),
|
|
48
|
+
)
|
|
49
|
+
cpSync(join(source, 'tsconfig.json'), join(fixture, 'tsconfig.json'))
|
|
50
|
+
writeFileSync(join(fixture, 'package-identity.json'), JSON.stringify(identity, null, 2))
|
|
51
|
+
// Native representative runs under central defaults, rather than the stress ceilings.
|
|
52
|
+
writeFileSync(
|
|
53
|
+
join(fixture, 'convex/querying.ts'),
|
|
54
|
+
`import { createQueryEngine } from 'cvx-kit/querying'
|
|
55
|
+
import schema from './schema'
|
|
56
|
+
import { relations } from './relations'
|
|
57
|
+
import { cursor } from './functions'
|
|
58
|
+
export const querying = createQueryEngine({ schema, relations, cursor })
|
|
59
|
+
`,
|
|
60
|
+
)
|
|
61
|
+
const originalRelations = readFileSync(join(fixture, 'convex/relations.ts'), 'utf8')
|
|
62
|
+
writeFileSync(join(fixture, 'convex/relations.ts'), originalRelations)
|
|
63
|
+
writeFileSync(join(fixture, 'convex/relationsEquivalent.ts'), originalRelations)
|
|
64
|
+
writeFileSync(
|
|
65
|
+
join(fixture, 'convex/relationsChanged.ts'),
|
|
66
|
+
originalRelations.replace(
|
|
67
|
+
'aliasLabels: r.many.labels({ from: r.tasks._id.through(r.edges.task), to: r.labels._id.through(r.edges.label) })',
|
|
68
|
+
'aliasLabels: r.many.labels({ from: r.tasks._id, to: r.labels.task })',
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
writeFileSync(
|
|
72
|
+
join(fixture, 'convex/api/probes.ts'),
|
|
73
|
+
`import { createQueryEngine } from 'cvx-kit/querying'
|
|
74
|
+
import { authQuery } from '../functions'
|
|
75
|
+
import { relations as equivalent } from '../relationsEquivalent'
|
|
76
|
+
import { relations as changed } from '../relationsChanged'
|
|
77
|
+
import schema from '../schema'
|
|
78
|
+
import { cursor } from '../functions'
|
|
79
|
+
import { contract } from '../domain/tasks/contracts'
|
|
80
|
+
const eq = createQueryEngine({ schema, relations: equivalent, cursor })
|
|
81
|
+
const different = createQueryEngine({ schema, relations: changed, cursor })
|
|
82
|
+
export const resume = authQuery({ args: contract.queries.list.input, returns: contract.queries.list.result, handler: (ctx,args) => eq(ctx).tasks.paginate(args) })
|
|
83
|
+
export const reject = authQuery({ args: contract.queries.list.input, returns: contract.queries.list.result, handler: (ctx,args) => different(ctx).tasks.paginate(args) })
|
|
84
|
+
`,
|
|
85
|
+
)
|
|
86
|
+
writeFileSync(
|
|
87
|
+
join(fixture, 'convex/seed.ts'),
|
|
88
|
+
`import { internalMutation } from './_generated/server'
|
|
89
|
+
export const seed = internalMutation({ args: {}, handler: async (ctx) => {
|
|
90
|
+
const tasks = []
|
|
91
|
+
for(let ordinal=0;ordinal<24;ordinal++) tasks.push(await ctx.db.insert('tasks',{ordinal,tenant:'a',department:'support',status:'open',active:false,priority:0,code:'',authorized:ordinal>=5,secret:'NATIVE_PRIVATE'}))
|
|
92
|
+
const labels=[]
|
|
93
|
+
for(let ordinal=0;ordinal<6;ordinal++) labels.push(await ctx.db.insert('labels',{ordinal,tenant:'a',authorized:ordinal!==5,name:'native-label-'+ordinal,task:tasks[5]}))
|
|
94
|
+
await ctx.db.patch(tasks[5],{reference:tasks[0],required:labels[0]})
|
|
95
|
+
for(let ordinal=0;ordinal<6;ordinal++) await ctx.db.insert('edges',{ordinal,task:tasks[5],label:labels[ordinal],authorized:ordinal!==4})
|
|
96
|
+
await ctx.db.insert('edges',{ordinal:6,task:tasks[5],label:labels[1],authorized:true})
|
|
97
|
+
return {tasks,labels}
|
|
98
|
+
} })
|
|
99
|
+
`,
|
|
100
|
+
)
|
|
101
|
+
// Generated by the native CLI before deployment; initial binding supports the HTTP seed route.
|
|
102
|
+
const serverPath = join(fixture, 'convex/_generated/server.ts')
|
|
103
|
+
writeFileSync(
|
|
104
|
+
serverPath,
|
|
105
|
+
readFileSync(serverPath, 'utf8') +
|
|
106
|
+
"\nexport { httpActionGeneric as httpAction } from 'convex/server'\n",
|
|
107
|
+
)
|
|
108
|
+
writeFileSync(
|
|
109
|
+
join(fixture, 'convex/http.ts'),
|
|
110
|
+
`import { httpRouter } from 'convex/server'
|
|
111
|
+
import { httpAction } from './_generated/server'
|
|
112
|
+
import { makeFunctionReference } from 'convex/server'
|
|
113
|
+
const router=httpRouter()
|
|
114
|
+
router.route({path:'/seed',method:'POST',handler:httpAction(async ctx=>new Response(JSON.stringify(await ctx.runMutation(makeFunctionReference<'mutation'>('seed:seed'),{})),{headers:{'content-type':'application/json'}}))})
|
|
115
|
+
export default router
|
|
116
|
+
`,
|
|
117
|
+
)
|
|
118
|
+
// Metadata comes from actual native registered functions, not a reduced hand-written validator.
|
|
119
|
+
writeFileSync(
|
|
120
|
+
join(fixture, 'metadata.mjs'),
|
|
121
|
+
`import { writeFileSync } from 'node:fs'; import * as api from './convex/api/tasks.ts';
|
|
122
|
+
for(const [name, fn] of Object.entries(api)) writeFileSync('validator-'+name+'.json',JSON.stringify({args:JSON.parse(fn.exportArgs()),returns:JSON.parse(fn.exportReturns())}));
|
|
123
|
+
`,
|
|
124
|
+
)
|
|
125
|
+
writeFileSync(
|
|
126
|
+
join(fixture, 'verify.mjs'),
|
|
127
|
+
`import assert from 'node:assert/strict';import {readFileSync,writeFileSync} from 'node:fs';import {spawnSync} from 'node:child_process';
|
|
128
|
+
const key=JSON.parse(readFileSync('.convex/local/default/config.json')).adminKey;
|
|
129
|
+
const who={subject:'native-reader',issuer:'https://fixture.test',tokenIdentifier:'https://fixture.test|native-reader',org_id:'a',role:'reader'};
|
|
130
|
+
const url='http://127.0.0.1:'+process.argv[2];const records=[];
|
|
131
|
+
async function call(path,label,args,valid=true,identity=who){
|
|
132
|
+
const started=performance.now();const res=await fetch(url+'/api/query',{method:'POST',headers:{'content-type':'application/json',...(identity?{Authorization:'Convex '+key+':'+Buffer.from(JSON.stringify(identity)).toString('base64')}:{})},body:JSON.stringify({path,args,format:'json'})});const raw=await res.text();writeFileSync('http-'+records.length+'-'+label+'.json',raw);const body=JSON.parse(raw);assert.equal(res.status,200);assert.equal(body.status,valid?'success':'error',label+': '+raw);assert.ok(!raw.includes('NATIVE_PRIVATE'));records.push({path,label,args,status:body.status,milliseconds:performance.now()-started,bytes:Buffer.byteLength(raw)});writeFileSync('native-results.json',JSON.stringify(records,null,2));return body;
|
|
133
|
+
}
|
|
134
|
+
// Local deployment registration also consumes the backend write-rate window.
|
|
135
|
+
await new Promise(resolve=>setTimeout(resolve,2000));
|
|
136
|
+
const seedResponse=await fetch('http://127.0.0.1:'+process.argv[3]+'/seed',{method:'POST'});const seedRaw=await seedResponse.text();writeFileSync('seed-envelope.json',seedRaw);assert.equal(seedResponse.status,200);const ids=JSON.parse(seedRaw);
|
|
137
|
+
for(const order of ['asc','desc']){let cursor=null;const actual=[];for(let page=0;page<30;page++){const body=await call('api/tasks:conditional','conditional-'+order+'-'+page,{numItems:7,cursor,order,filters:{active:false}});actual.push(...body.value.page.map(r=>r._id));if(body.value.isDone)break;assert.notEqual(body.value.continueCursor,cursor);cursor=body.value.continueCursor;}const expected=ids.tasks.slice(5);if(order==='desc')expected.reverse();assert.deepEqual(actual,expected);}
|
|
138
|
+
const first=(await call('api/tasks:list','first',{numItems:1,cursor:null})).value;
|
|
139
|
+
assert.match(first.continueCursor,/^pc\\./);assert.ok(!first.continueCursor.includes(ids.tasks[5]));
|
|
140
|
+
await call('api/probes:resume','equivalent-registry',{numItems:7,cursor:first.continueCursor});
|
|
141
|
+
await call('api/probes:reject','changed-registry',{numItems:7,cursor:first.continueCursor},false);
|
|
142
|
+
for(const [label,args,identity] of [['tamper',{numItems:1,cursor:first.continueCursor+'!'},who],['filter',{numItems:1,cursor:first.continueCursor,filters:{ordinal:6}},who],['order',{numItems:1,cursor:first.continueCursor,order:'desc'},who],['tenant',{numItems:1,cursor:first.continueCursor},{...who,org_id:'b'}],['principal',{numItems:1,cursor:first.continueCursor},{...who,subject:'other',tokenIdentifier:'other'}]]){const body=await call('api/tasks:list',label,args,false,identity);assert.match(body.errorMessage,/Invalid cursor/);}
|
|
143
|
+
await call('api/tasks:list','no-identity',{numItems:1,cursor:null},false,null);
|
|
144
|
+
await call('api/tasks:list','unsupported-role',{numItems:1,cursor:null},false,{...who,role:'unknown'});
|
|
145
|
+
const expanded=(await call('api/tasks:expanded','expanded',{numItems:7,cursor:null,filters:{ordinal:5}})).value.page[0];assert.equal(expanded.referenceTask,null);assert.equal(expanded.requiredLabel._id,ids.labels[0]);assert.equal(expanded.labels.page.length,5);assert.ok(expanded.labels.page.every(r=>r.owner._id===ids.tasks[5]));assert.deepEqual(expanded.labels.page.map(r=>r._id),expanded.aliasLabels.page.map(r=>r._id));
|
|
146
|
+
const predicate=(await call('api/tasks:list','nested-predicate',{numItems:7,cursor:null,filters:{labels:{some:{ordinal:1,owner:{ordinal:5}}}}})).value;assert.deepEqual(predicate.page.map(r=>r._id),[ids.tasks[5]]);
|
|
147
|
+
let cursor=null;const child=[];for(let page=0;page<10;page++){const value=(await call('api/tasks:next','child-'+page,{parent:ids.tasks[5],numItems:1,cursor})).value;child.push(...value.page.map(r=>r._id));if(value.isDone)break;cursor=value.continueCursor;}assert.deepEqual(child,[...ids.labels.slice(0,4),ids.labels[1]]);
|
|
148
|
+
await call('api/tasks:next','cross-parent',{parent:ids.tasks[6],numItems:1,cursor},false);
|
|
149
|
+
for(const [label,args] of [['wrong-scalar',{numItems:1,cursor:null,filters:{referenceTask:{ordinal:'wrong'}}}],['unknown-operator',{numItems:1,cursor:null,filters:{ordinal:{bad:1}}}],['bad-options',{numItems:0,cursor:null}],['unknown-field',{numItems:1,cursor:null,filters:{unknown:1}}]])await call('api/tasks:list',label,args,false);
|
|
150
|
+
const wrongTable=await call('api/tasks:next','wrong-table-parent',{parent:ids.labels[0],numItems:1,cursor:null},false);assert.match(wrongTable.errorMessage,/ArgumentValidationError/);assert.ok(!wrongTable.errorMessage.includes('Invalid cursor'));
|
|
151
|
+
const split=(await call('api/tasks:list','native-limit',{numItems:100,cursor:null,maximumRowsRead:8})).value;assert.match(split.splitCursor,/^pc\\./);assert.ok(['SplitRequired','SplitRecommended'].includes(split.pageStatus));
|
|
152
|
+
const left=(await call('api/tasks:list','end-left',{numItems:100,cursor:null,endCursor:split.splitCursor})).value;const right=(await call('api/tasks:list','end-right',{numItems:100,cursor:split.splitCursor,endCursor:split.continueCursor})).value;assert.deepEqual([...left.page,...right.page],split.page);
|
|
153
|
+
const cli=spawnSync(process.execPath,['node_modules/convex/bin/main.js','run','api/tasks:list',JSON.stringify({numItems:1,cursor:null}),'--identity',JSON.stringify(who)],{encoding:'utf8',timeout:30000});writeFileSync('cli-identity.stdout.log',cli.stdout);writeFileSync('cli-identity.stderr.log',cli.stderr);assert.equal(cli.status,0);assert.equal(JSON.parse(cli.stdout).page[0]._id,ids.tasks[5]);
|
|
154
|
+
writeFileSync('NATIVE_SUCCESS',JSON.stringify({cases:records.length,cliIdentity:true}));console.log(records.length+' native HTTP envelopes passed');
|
|
155
|
+
`,
|
|
156
|
+
)
|
|
157
|
+
const files = (dir) =>
|
|
158
|
+
readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
|
|
159
|
+
e.isDirectory() ? files(join(dir, e.name)) : [join(dir, e.name)],
|
|
160
|
+
)
|
|
161
|
+
const paths = files(join(fixture, 'convex')).filter((p) => !p.includes('/_generated/'))
|
|
162
|
+
const hashes = () =>
|
|
163
|
+
Object.fromEntries(paths.map((p) => [relative(fixture, p), hash(readFileSync(p))]))
|
|
164
|
+
const before = hashes()
|
|
165
|
+
writeFileSync(join(fixture, 'source-before.json'), JSON.stringify(before, null, 2))
|
|
166
|
+
execFileSync(process.execPath, ['--import', './register.mjs', 'metadata.mjs'], {
|
|
167
|
+
cwd: fixture,
|
|
168
|
+
stdio: 'pipe',
|
|
169
|
+
})
|
|
170
|
+
const metadata = files(fixture).filter((p) => /validator-.*\.json$/.test(p))
|
|
171
|
+
writeFileSync(
|
|
172
|
+
join(fixture, 'native-validator-hashes.json'),
|
|
173
|
+
JSON.stringify(
|
|
174
|
+
metadata.map((p) => ({
|
|
175
|
+
path: relative(fixture, p),
|
|
176
|
+
bytes: readFileSync(p).length,
|
|
177
|
+
sha256: hash(readFileSync(p)),
|
|
178
|
+
})),
|
|
179
|
+
null,
|
|
180
|
+
2,
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
const env = localConvexEnvironment()
|
|
184
|
+
assertIsolatedConvexFixture(fixture)
|
|
185
|
+
async function freePort(requested = 0) {
|
|
186
|
+
const server = createServer()
|
|
187
|
+
await new Promise((done, fail) => {
|
|
188
|
+
server.once('error', fail)
|
|
189
|
+
server.listen(requested, '127.0.0.1', done)
|
|
190
|
+
})
|
|
191
|
+
const port = server.address().port
|
|
192
|
+
await new Promise((done) => server.close(done))
|
|
193
|
+
return port
|
|
194
|
+
}
|
|
195
|
+
const cloud = await freePort()
|
|
196
|
+
let site = await freePort()
|
|
197
|
+
while (site === cloud) site = await freePort()
|
|
198
|
+
const commands = []
|
|
199
|
+
const processes = []
|
|
200
|
+
async function run(label, args) {
|
|
201
|
+
commands.push([process.execPath, 'node_modules/convex/bin/main.js', ...args])
|
|
202
|
+
writeFileSync(join(fixture, 'commands.json'), JSON.stringify(commands, null, 2))
|
|
203
|
+
const result = await runOwnedProcess({
|
|
204
|
+
command: process.execPath,
|
|
205
|
+
args: commands.at(-1).slice(1),
|
|
206
|
+
cwd: fixture,
|
|
207
|
+
env,
|
|
208
|
+
onSpawn: (pid) => processes.push(pid),
|
|
209
|
+
})
|
|
210
|
+
const { stdout, stderr, ...evidence } = result
|
|
211
|
+
writeFileSync(join(fixture, label + '.stdout.log'), stdout)
|
|
212
|
+
writeFileSync(join(fixture, label + '.stderr.log'), stderr)
|
|
213
|
+
writeFileSync(join(fixture, label + '-result.json'), JSON.stringify(evidence, null, 2))
|
|
214
|
+
assert.equal(result.code, 0, label + ' failed; retained logs')
|
|
215
|
+
assert.equal(result.timedOut, false, label + ' timed out; retained logs')
|
|
216
|
+
assert.equal(result.cleanup?.alive, false, label + ' owned group survived cleanup')
|
|
217
|
+
console.log(label + ': exit 0')
|
|
218
|
+
}
|
|
219
|
+
let originalFailure
|
|
220
|
+
try {
|
|
221
|
+
await run('deploy', [
|
|
222
|
+
'dev',
|
|
223
|
+
'--once',
|
|
224
|
+
'--typecheck=disable',
|
|
225
|
+
'--local-cloud-port',
|
|
226
|
+
String(cloud),
|
|
227
|
+
'--local-site-port',
|
|
228
|
+
String(site),
|
|
229
|
+
])
|
|
230
|
+
assertLocalConvexState(fixture)
|
|
231
|
+
const cfg = JSON.parse(readFileSync(join(fixture, '.convex/local/default/config.json')))
|
|
232
|
+
assert.equal(cfg.ports.cloud, cloud)
|
|
233
|
+
assert.equal(cfg.ports.site, site)
|
|
234
|
+
writeFileSync(
|
|
235
|
+
join(fixture, 'local-binding.json'),
|
|
236
|
+
JSON.stringify({ ports: cfg.ports, backendVersion: cfg.backendVersion }),
|
|
237
|
+
)
|
|
238
|
+
execFileSync(join(source, 'node_modules/.bin/tsc'), ['--project', 'tsconfig.json'], {
|
|
239
|
+
cwd: fixture,
|
|
240
|
+
stdio: 'pipe',
|
|
241
|
+
})
|
|
242
|
+
writeFileSync(
|
|
243
|
+
join(fixture, 'strict-types-result.json'),
|
|
244
|
+
JSON.stringify({ exitCode: 0, strict: true, skipLibCheck: false }),
|
|
245
|
+
)
|
|
246
|
+
await run('runtime', [
|
|
247
|
+
'dev',
|
|
248
|
+
'--once',
|
|
249
|
+
'--typecheck=disable',
|
|
250
|
+
'--start',
|
|
251
|
+
`node verify.mjs ${cloud} ${site}`,
|
|
252
|
+
])
|
|
253
|
+
assert.ok(existsSync(join(fixture, 'NATIVE_SUCCESS')))
|
|
254
|
+
const rows = JSON.parse(readFileSync(join(fixture, 'native-results.json')))
|
|
255
|
+
assert.ok(rows.length >= 25)
|
|
256
|
+
assert.equal(files(fixture).filter((p) => /http-\d+-.*\.json$/.test(p)).length, rows.length)
|
|
257
|
+
const key = JSON.parse(readFileSync(join(fixture, '.convex/local/default/config.json'))).adminKey
|
|
258
|
+
for (const path of files(fixture).filter(
|
|
259
|
+
(p) => /\.(log|json)$/.test(p) && !p.includes('/.convex/'),
|
|
260
|
+
)) {
|
|
261
|
+
const bytes = readFileSync(path, 'utf8')
|
|
262
|
+
assert.ok(!bytes.includes(key))
|
|
263
|
+
assert.ok(!bytes.includes('Authorization: Convex'))
|
|
264
|
+
}
|
|
265
|
+
assert.deepEqual(hashes(), before)
|
|
266
|
+
assert.equal(hash(readFileSync(identity.tarball)), identity.sha256)
|
|
267
|
+
writeFileSync(join(fixture, 'source-after.json'), JSON.stringify(hashes(), null, 2))
|
|
268
|
+
} catch (error) {
|
|
269
|
+
originalFailure = error
|
|
270
|
+
throw error
|
|
271
|
+
} finally {
|
|
272
|
+
const cleanup = await collectOwnedCleanup({
|
|
273
|
+
pids: processes,
|
|
274
|
+
ports: [cloud, site],
|
|
275
|
+
checkPort: freePort,
|
|
276
|
+
failure: originalFailure,
|
|
277
|
+
})
|
|
278
|
+
writeFileSync(join(fixture, 'cleanup.json'), JSON.stringify(cleanup, null, 2))
|
|
279
|
+
// Preserve the original proof error; cleanup diagnostics remain independently recorded.
|
|
280
|
+
if (!originalFailure) {
|
|
281
|
+
assert.ok(
|
|
282
|
+
cleanup.groups.every((group) => !group.alive && !group.error && group.errors.length === 0),
|
|
283
|
+
'Owned process cleanup failed',
|
|
284
|
+
)
|
|
285
|
+
assert.ok(
|
|
286
|
+
cleanup.ports.every((port) => port.closed),
|
|
287
|
+
'Owned ports remain occupied',
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
writeFileSync(
|
|
292
|
+
join(fixture, 'SUCCESS'),
|
|
293
|
+
'Bounded exact packed default runtime integrated proof passed.\n',
|
|
294
|
+
)
|
|
295
|
+
execFileSync(process.execPath, ['audit.mjs'], { cwd: fixture, stdio: 'pipe' })
|
|
296
|
+
console.log(`Native proof complete: ${fixture}`)
|
|
@@ -144,6 +144,8 @@ import { workflowTransition } from 'cvx-kit/workflow'
|
|
|
144
144
|
import { selectOperation, bindOperationExecutor, createOperationTools, operationToolDialect } from 'cvx-kit/agent-tools'
|
|
145
145
|
import { actionGeneric, queryGeneric, mutationGeneric, internalActionGeneric, internalQueryGeneric, internalMutationGeneric } from 'convex/server'
|
|
146
146
|
import { z } from 'zod'
|
|
147
|
+
import { convexTest } from 'convex-test'
|
|
148
|
+
import { defineSchema, anyApi } from 'convex/server'
|
|
147
149
|
const require = createRequire(import.meta.url)
|
|
148
150
|
if (process.argv.includes('--without-effect')) {
|
|
149
151
|
assert.throws(() => require.resolve('effect'), { code: 'MODULE_NOT_FOUND' }, 'optional peer was unexpectedly auto-installed')
|
|
@@ -187,7 +189,11 @@ const auth = createAuthFunctions({
|
|
|
187
189
|
getAuthUser: async () => ({ id: 'actor' }), mapRole: defaultRoleMap, adminRoles: ['admin'],
|
|
188
190
|
})
|
|
189
191
|
const authenticated = auth.authQuery({ args: {}, returns: z.string(), handler: ctx => ctx.actor.userId + ':' + ctx.tenant })
|
|
190
|
-
|
|
192
|
+
const authTest = convexTest(defineSchema({}), {
|
|
193
|
+
'./_generated/server.js': async () => ({ query: queryGeneric }),
|
|
194
|
+
'./auth.js': async () => ({ authenticated }),
|
|
195
|
+
})
|
|
196
|
+
assert.equal(await authTest.withIdentity({ subject: 'actor', org_id: 'org', role: 'member' }).query(anyApi.auth.authenticated, {}), 'actor:org')
|
|
191
197
|
const table = zodTable('notes', () => ({ text: z.string() }), { commandFields: ['text'] })
|
|
192
198
|
const crud = createCrudCommands({ Command, table, aggregateType: 'note', actor: () => 'actor' })
|
|
193
199
|
const changes = []
|
|
@@ -375,6 +381,7 @@ try {
|
|
|
375
381
|
'cvx-kit': `file:${join(temporary, `cvx-kit-${manifest.version}.tgz`)}`,
|
|
376
382
|
convex: manifest.devDependencies.convex,
|
|
377
383
|
'convex-helpers': helpersVersion,
|
|
384
|
+
'convex-test': '0.0.61',
|
|
378
385
|
'@convex-dev/workflow': manifest.dependencies['@convex-dev/workflow'],
|
|
379
386
|
zod: manifest.devDependencies.zod,
|
|
380
387
|
}
|
|
@@ -9,7 +9,7 @@ const root = join(import.meta.dirname, '..')
|
|
|
9
9
|
const temporary = mkdtempSync(join(tmpdir(), 'cvx-kit-oxlint-'))
|
|
10
10
|
const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
11
11
|
const ruleNames = Object.keys(rules)
|
|
12
|
-
assert.equal(ruleNames.length,
|
|
12
|
+
assert.equal(ruleNames.length, 23)
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
15
|
run('bun', ['pm', 'pack', '--destination', temporary, '--ignore-scripts'], root)
|
|
@@ -79,6 +79,7 @@ export const value = privateHelper()
|
|
|
79
79
|
import { makeFunctionReference as ref } from 'convex/server'
|
|
80
80
|
import { z } from 'zod'
|
|
81
81
|
import { createAuthFunctions } from 'cvx-kit/auth'
|
|
82
|
+
import { zodTable } from 'cvx-kit/zod-table'
|
|
82
83
|
import { other } from '../domain/users/commands'
|
|
83
84
|
export const save = mutation({ handler: async (ctx) => {
|
|
84
85
|
await ctx.db.insert('orders', { updatedAt: Date.now() })
|
|
@@ -88,6 +89,9 @@ export const constructors = createAuthFunctions({})
|
|
|
88
89
|
export const state = z.enum(['draft'])
|
|
89
90
|
export const target = ref('orders:save')
|
|
90
91
|
export const value = other
|
|
92
|
+
const orders = zodTable('orders', () => ({ name: z.string() }))
|
|
93
|
+
export const dto = { ...orders.toPublicDto(row), _id: row._id }
|
|
94
|
+
export const pagination = z.object({ numItems: z.number(), cursor: z.string().nullable() })
|
|
91
95
|
`,
|
|
92
96
|
)
|
|
93
97
|
mkdirSync(join(fixture, 'convex/application'), { recursive: true })
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { execFileSync } from 'node:child_process'
|
|
3
|
+
import { createHash } from 'node:crypto'
|
|
4
|
+
import { cpSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
5
|
+
import { join, resolve, relative } from 'node:path'
|
|
6
|
+
|
|
7
|
+
const root = resolve(import.meta.dirname, '..')
|
|
8
|
+
const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
9
|
+
const proof = mkdtempSync('/tmp/cvx-kit-querying-')
|
|
10
|
+
console.log(`U8 retained proof: ${proof}`)
|
|
11
|
+
const hash = (bytes) => createHash('sha256').update(bytes).digest('hex')
|
|
12
|
+
function files(directory) {
|
|
13
|
+
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
14
|
+
const path = join(directory, entry.name)
|
|
15
|
+
return entry.isDirectory() ? files(path) : [path]
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
function run(command, args, cwd, label) {
|
|
19
|
+
const started = performance.now()
|
|
20
|
+
try {
|
|
21
|
+
const output = execFileSync(command, args, {
|
|
22
|
+
cwd,
|
|
23
|
+
encoding: 'utf8',
|
|
24
|
+
stdio: 'pipe',
|
|
25
|
+
timeout: 1200000,
|
|
26
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
27
|
+
})
|
|
28
|
+
writeFileSync(join(proof, `${label}.log`), output)
|
|
29
|
+
console.log(`${label}: exit 0 (${((performance.now() - started) / 1000).toFixed(2)}s)`)
|
|
30
|
+
return output
|
|
31
|
+
} catch (error) {
|
|
32
|
+
// Install output may contain credentials from caller's package-manager setup.
|
|
33
|
+
if (label.endsWith('install'))
|
|
34
|
+
writeFileSync(join(proof, `${label}.log`), `Install failed: exit ${error.status}\n`)
|
|
35
|
+
else writeFileSync(join(proof, `${label}.log`), `${error.stdout ?? ''}\n${error.stderr ?? ''}`)
|
|
36
|
+
throw new Error(`${label} failed; evidence retained at ${proof}`, { cause: error.status })
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const sourcePaths = [
|
|
40
|
+
'README.md',
|
|
41
|
+
'package.json',
|
|
42
|
+
'vite.config.ts',
|
|
43
|
+
...files(join(root, 'src'))
|
|
44
|
+
.filter((p) => p.endsWith('.ts'))
|
|
45
|
+
.map((p) => relative(root, p)),
|
|
46
|
+
...files(join(root, 'test/fixture-packed-querying')).map((p) => relative(root, p)),
|
|
47
|
+
'scripts/test-packed-querying.mjs',
|
|
48
|
+
'scripts/smoke-packed-querying.mjs',
|
|
49
|
+
'scripts/owned-process.mjs',
|
|
50
|
+
'scripts/owned-process.test.mjs',
|
|
51
|
+
'scripts/test-packed-oxlint.mjs',
|
|
52
|
+
]
|
|
53
|
+
const sourceHashes = () =>
|
|
54
|
+
Object.fromEntries(sourcePaths.map((p) => [p, hash(readFileSync(join(root, p)))]))
|
|
55
|
+
const before = sourceHashes()
|
|
56
|
+
writeFileSync(join(proof, 'source-before.json'), JSON.stringify(before, null, 2))
|
|
57
|
+
run('bun', ['pm', 'pack', '--ignore-scripts', '--destination', proof], root, 'pack')
|
|
58
|
+
const tarball = join(proof, `${manifest.name}-${manifest.version}.tgz`)
|
|
59
|
+
writeFileSync(
|
|
60
|
+
join(proof, 'package-identity.json'),
|
|
61
|
+
JSON.stringify(
|
|
62
|
+
{
|
|
63
|
+
name: manifest.name,
|
|
64
|
+
version: manifest.version,
|
|
65
|
+
tarball,
|
|
66
|
+
sha256: hash(readFileSync(tarball)),
|
|
67
|
+
head: execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(),
|
|
68
|
+
files: run('tar', ['-tzf', tarball], root, 'tar-manifest').trim().split('\n'),
|
|
69
|
+
},
|
|
70
|
+
null,
|
|
71
|
+
2,
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
for (const [label, helpers, zod] of [
|
|
75
|
+
['installed', '0.1.128', '4.6.5'],
|
|
76
|
+
['minimum', '0.1.127', '4.5.4'],
|
|
77
|
+
]) {
|
|
78
|
+
const fixture = join(proof, label)
|
|
79
|
+
mkdirSync(fixture)
|
|
80
|
+
writeFileSync(
|
|
81
|
+
join(fixture, 'package.json'),
|
|
82
|
+
JSON.stringify({
|
|
83
|
+
private: true,
|
|
84
|
+
type: 'module',
|
|
85
|
+
dependencies: {
|
|
86
|
+
'cvx-kit': `file:${tarball}`,
|
|
87
|
+
convex: '1.46.0',
|
|
88
|
+
'convex-helpers': helpers,
|
|
89
|
+
zod,
|
|
90
|
+
typescript: '7.0.2',
|
|
91
|
+
'convex-test': '0.0.61',
|
|
92
|
+
'@types/node': manifest.devDependencies['@types/node'],
|
|
93
|
+
oxlint: manifest.devDependencies.oxlint,
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
)
|
|
97
|
+
const templates = join(root, 'test/fixture-packed-querying')
|
|
98
|
+
for (const source of files(templates)) {
|
|
99
|
+
const destination = join(fixture, relative(templates, source).replace(/\.template$/, ''))
|
|
100
|
+
mkdirSync(resolve(destination, '..'), { recursive: true })
|
|
101
|
+
cpSync(source, destination)
|
|
102
|
+
}
|
|
103
|
+
mkdirSync(join(fixture, 'convex/_generated'), { recursive: true })
|
|
104
|
+
writeFileSync(
|
|
105
|
+
join(fixture, 'convex/_generated/dataModel.ts'),
|
|
106
|
+
`import type {DataModelFromSchemaDefinition} from 'convex/server'; import schema from '../schema'; export type DataModel = DataModelFromSchemaDefinition<typeof schema>\n`,
|
|
107
|
+
)
|
|
108
|
+
writeFileSync(
|
|
109
|
+
join(fixture, 'convex/_generated/server.ts'),
|
|
110
|
+
`import {queryGeneric, mutationGeneric, actionGeneric, internalQueryGeneric, internalMutationGeneric, internalActionGeneric, type GenericQueryCtx} from 'convex/server'; import type {DataModel} from './dataModel'; export const query = queryGeneric; export const mutation = mutationGeneric; export const action = actionGeneric; export const internalQuery = internalQueryGeneric; export const internalMutation = internalMutationGeneric; export const internalAction = internalActionGeneric; export type QueryCtx = GenericQueryCtx<DataModel>\n`,
|
|
111
|
+
)
|
|
112
|
+
writeFileSync(
|
|
113
|
+
join(fixture, 'tsconfig.json'),
|
|
114
|
+
JSON.stringify({
|
|
115
|
+
compilerOptions: {
|
|
116
|
+
strict: true,
|
|
117
|
+
types: ['node'],
|
|
118
|
+
skipLibCheck: false,
|
|
119
|
+
noEmit: true,
|
|
120
|
+
target: 'ES2025',
|
|
121
|
+
module: 'ESNext',
|
|
122
|
+
moduleResolution: 'bundler',
|
|
123
|
+
allowImportingTsExtensions: true,
|
|
124
|
+
},
|
|
125
|
+
include: ['convex/**/*.ts', 'types.ts', 'runtime.ts', 'focused.ts'],
|
|
126
|
+
}),
|
|
127
|
+
)
|
|
128
|
+
run('bun', ['install', '--ignore-scripts'], fixture, `${label}-install`)
|
|
129
|
+
const versions = Object.fromEntries(
|
|
130
|
+
['cvx-kit', 'convex', 'convex-helpers', 'zod', 'typescript', 'convex-test'].map((name) => [
|
|
131
|
+
name,
|
|
132
|
+
JSON.parse(readFileSync(join(fixture, 'node_modules', name, 'package.json'), 'utf8')).version,
|
|
133
|
+
]),
|
|
134
|
+
)
|
|
135
|
+
writeFileSync(join(fixture, 'versions.json'), JSON.stringify(versions, null, 2))
|
|
136
|
+
run(
|
|
137
|
+
join(fixture, 'node_modules/.bin/tsc'),
|
|
138
|
+
['--project', 'tsconfig.json'],
|
|
139
|
+
fixture,
|
|
140
|
+
`${label}-types`,
|
|
141
|
+
)
|
|
142
|
+
const consumerRules = JSON.parse(
|
|
143
|
+
run(
|
|
144
|
+
'node',
|
|
145
|
+
[
|
|
146
|
+
'--input-type=module',
|
|
147
|
+
'-e',
|
|
148
|
+
"import plugin from 'cvx-kit/oxlint'; console.log(JSON.stringify(Object.keys(plugin.rules)))",
|
|
149
|
+
],
|
|
150
|
+
fixture,
|
|
151
|
+
`${label}-consumer-rules`,
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
assert.equal(consumerRules.length, 23, 'Run every exported packaged consumer rule')
|
|
155
|
+
writeFileSync(
|
|
156
|
+
join(fixture, '.oxlintrc.json'),
|
|
157
|
+
JSON.stringify({
|
|
158
|
+
jsPlugins: [{ name: 'cvx', specifier: 'cvx-kit/oxlint' }],
|
|
159
|
+
rules: Object.fromEntries(consumerRules.map((r) => [`cvx/${r}`, 'error'])),
|
|
160
|
+
}),
|
|
161
|
+
)
|
|
162
|
+
run(
|
|
163
|
+
join(fixture, 'node_modules/.bin/oxlint'),
|
|
164
|
+
['--config', '.oxlintrc.json', 'convex'],
|
|
165
|
+
fixture,
|
|
166
|
+
`${label}-consumer-lint`,
|
|
167
|
+
)
|
|
168
|
+
const tests = files(join(fixture, 'convex'))
|
|
169
|
+
.filter((p) => p.endsWith('.test.ts'))
|
|
170
|
+
.map((p) => relative(fixture, p))
|
|
171
|
+
assert.equal(tests.length, 3)
|
|
172
|
+
run(
|
|
173
|
+
'node',
|
|
174
|
+
['--import', './register.mjs', '--test', ...tests],
|
|
175
|
+
fixture,
|
|
176
|
+
`${label}-colocated-tests`,
|
|
177
|
+
)
|
|
178
|
+
run('node', ['--import', './register.mjs', 'runtime.ts'], fixture, `${label}-runtime`)
|
|
179
|
+
run('node', ['--import', './register.mjs', 'focused.ts'], fixture, `${label}-focused`)
|
|
180
|
+
}
|
|
181
|
+
const after = sourceHashes()
|
|
182
|
+
writeFileSync(join(proof, 'source-after.json'), JSON.stringify(after, null, 2))
|
|
183
|
+
assert.deepEqual(after, before, 'source must stay fixed during packed verification')
|
|
184
|
+
writeFileSync(join(proof, 'SUCCESS'), 'Both strict packed peers and full runtime stress passed.\n')
|
|
185
|
+
console.log(`Packed querying complete. Evidence retained at ${proof}`)
|