cloudkit 0.9.1 → 0.10.0

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.
data/CHANGES CHANGED
@@ -1,3 +1,9 @@
1
+ 0.10.0
2
+ - Updated for Rack 0.9
3
+ - Added batch URI resolution for resource collections
4
+ - Refactored use of constants
5
+ - Updated documentation
6
+
1
7
  0.9.1
2
8
  - Fixed Rack::Lint/rackup errors related to Content-Type headers
3
9
  - Patched Rack to support StringIO#string in Rack::Lint::InputWrapper
data/COPYING CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008 Jon Crosby http://joncrosby.me
1
+ Copyright (c) 2008, 2009 Jon Crosby http://joncrosby.me
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README CHANGED
@@ -24,7 +24,7 @@ In a different rackup file:
24
24
  require 'cloudkit'
25
25
  contain :notes, :todos
26
26
 
27
- The aboves provides the same API as example 1 with added authentication and authorization via OpenID and OAuth, respectively.
27
+ The above provides the same API as example 1 with added authentication and authorization via OpenID and OAuth, respectively.
28
28
 
29
29
  An explicit version of example 2, minus the default developer landing page:
30
30
 
@@ -33,19 +33,19 @@ An explicit version of example 2, minus the default developer landing page:
33
33
  use CloudKit::OAuthFilter
34
34
  use CloudKit::OpenIDFilter
35
35
  use CloudKit::Service, :collections => [:notes, :todos]
36
- run lambda {|env| [200, {'Content-Type' => 'text/html'}, ['HELLO']]}
36
+ run lambda {|env| [200, {'Content-Type' => 'text/html', 'Content-Length' => '5'}, ['HELLO']]}
37
37
 
38
38
  The same as above, using MySQL:
39
39
 
40
40
  require 'cloudkit'
41
- use Rack::Config { |env|
42
- env['cloudkit.storage.uri'] = 'mysql://user:pass@localhost/cloudkit_example'
43
- }
41
+ use Rack::Config do |env|
42
+ env[CLOUDKIT_STORAGE_URI] = 'mysql://user:pass@localhost/cloudkit_example'
43
+ end
44
44
  use Rack::Pool::Session
45
45
  use CloudKit::OAuthFilter
46
46
  use CloudKit::OpenIDFilter
47
47
  use CloudKit::Service, :collections => [:notes, :todos]
48
- run lambda {|env| [200, {'Content-Type' => 'text/html'}, ['HELLO']]}
48
+ run lambda {|env| [200, {'Content-Type' => 'text/html', 'Content-Length' => '5'}, ['HELLO']]}
49
49
 
50
50
  See the examples directory for more.
51
51
 
@@ -61,7 +61,7 @@ Source: http://github.com/jcrosby/cloudkit
61
61
 
62
62
  ===License
63
63
 
64
- Copyright (c) 2008 Jon Crosby http://joncrosby.me
64
+ Copyright (c) 2008, 2009 Jon Crosby http://joncrosby.me
65
65
 
66
66
  Permission is hereby granted, free of charge, to any person obtaining
67
67
  a copy of this software and associated documentation files (the
data/TODO CHANGED
@@ -2,9 +2,6 @@
2
2
  - jquery.cloudkit
3
3
  titanium/gears
4
4
  - openid sreg
5
- - titanium middleware
6
- version/upgrade middleware
7
- rake automation
8
5
  - oauth token management
9
6
  - oauth consumer registration
10
7
  - acls i.e. allowed methods per user per uri
@@ -14,9 +11,13 @@
14
11
  - custom templates for openid / oauth
15
12
 
16
13
  Backlog
14
+ - batch updates (post, put, delete)
17
15
  - expect header/100-continue
18
16
  - deployable gem + admin app
19
17
  - cappuccino adapter
20
18
  - sproutcore adapter
21
19
  - tokyocabinet
22
20
  - ldap adapter for UserStore
21
+ - titanium middleware
22
+ version/upgrade middleware
23
+ rake automation
@@ -2,8 +2,8 @@ Gem::Specification.new do |s|
2
2
  s.specification_version = 2 if s.respond_to? :specification_version=
3
3
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
4
  s.name = "cloudkit"
5
- s.version = "0.9.1"
6
- s.date = "2008-12-31"
5
+ s.version = "0.10.0"
6
+ s.date = "2008-01-09"
7
7
  s.summary = "An Open Web JSON Appliance."
8
8
  s.description = "An Open Web JSON Appliance."
9
9
  s.authors = ["Jon Crosby"]
@@ -32,13 +32,13 @@ Gem::Specification.new do |s|
32
32
  examples/6.ru
33
33
  examples/TOC
34
34
  lib/cloudkit.rb
35
+ lib/cloudkit/constants.rb
35
36
  lib/cloudkit/flash_session.rb
36
37
  lib/cloudkit/oauth_filter.rb
37
38
  lib/cloudkit/oauth_store.rb
38
39
  lib/cloudkit/openid_filter.rb
39
40
  lib/cloudkit/openid_store.rb
40
41
  lib/cloudkit/rack/builder.rb
41
- lib/cloudkit/rack/lint.rb
42
42
  lib/cloudkit/rack/router.rb
43
43
  lib/cloudkit/request.rb
44
44
  lib/cloudkit/service.rb
@@ -73,7 +73,7 @@ Gem::Specification.new do |s|
73
73
  s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/}
74
74
  s.rubyforge_project = "cloudkit"
75
75
  s.rubygems_version = "1.1.1"
76
- s.add_dependency 'rack', '~> 0.4'
76
+ s.add_dependency 'rack', '~> 0.9'
77
77
  s.add_dependency 'rack-config', '>= 0.9'
78
78
  s.add_dependency 'uuid', '= 2.0.1'
79
79
  s.add_dependency 'sequel', '= 2.6.0'
@@ -37,6 +37,17 @@ If you haven't already installed the gem:
37
37
  </div>
38
38
  </p>
39
39
 
40
+ <p>
41
+ If you already have the gem, make sure you're running the latest version (0.10.0):
42
+ <div class="code">
43
+ $ gem list cloudkit<br/>
44
+ cloudkit (0.9.1) &lt;-- need to upgrade<br/>
45
+ $ gem update cloudkit<br/>
46
+ $ gem list cloudkit<br/>
47
+ cloudkit (0.10.0, 0.9.1) &lt;-- 0.10.0 is now in the list
48
+ </div>
49
+ </p>
50
+
40
51
  <p>
41
52
  Create a rackup file named config.ru, containing these two lines of code:
42
53
  <div class="code">
@@ -46,9 +57,9 @@ Create a rackup file named config.ru, containing these two lines of code:
46
57
  </p>
47
58
 
48
59
  <p>
49
- Run the app using <a href="http://code.macournoyer.com/thin">Thin</a>:
60
+ Run the app:
50
61
  <div class="code">
51
- $ thin start -R config.ru
62
+ $ rackup config.ru
52
63
  </div>
53
64
  </p>
54
65
 
@@ -56,13 +67,12 @@ Run the app using <a href="http://code.macournoyer.com/thin">Thin</a>:
56
67
  CloudKit is discoverable from top to bottom. Let's see what resource collections
57
68
  we're hosting:
58
69
  <div class="code">
59
- $ curl -i http://localhost:3000/cloudkit-meta<br/>
70
+ $ curl -i http://localhost:9292/cloudkit-meta<br/>
60
71
  HTTP/1.1 200 OK<br/>
61
72
  ETag: "ef2f29b1834ef8c2bf0d8f1abb100177"<br/>
62
73
  Cache-Control: proxy-revalidate<br/>
63
74
  Content-Type: application/json<br/>
64
- Content-Length: 20<br/>
65
- Connection: keep-alive<br/><br/>
75
+ Content-Length: 20<br/><br/>
66
76
 
67
77
  {"uris":["\/notes"]}
68
78
  </div>
@@ -71,39 +81,47 @@ we're hosting:
71
81
  <p>
72
82
  See what we can do with these note resources:
73
83
  <div class="code">
74
- $ curl -i -XOPTIONS http://localhost:3000/notes<br/>
84
+ $ curl -i -XOPTIONS http://localhost:9292/notes<br/>
75
85
  HTTP/1.1 200 OK<br/>
76
86
  Content-Length: 0<br/>
77
87
  Content-Type: application/json<br/>
78
88
  Allow: GET, HEAD, POST, OPTIONS<br/>
79
- Connection: keep-alive<br/>
80
89
  </div>
81
90
  </p>
82
91
 
83
92
  <p>
84
93
  List the currently available notes:
85
94
  <div class="code">
86
- $ curl -i http://localhost:3000/notes<br/>
95
+ $ curl -i http://localhost:9292/notes<br/>
87
96
  HTTP/1.1 200 OK<br/>
88
- ETag: "df392c5664e6ecd64b83210fb925f6c8"<br/>
97
+ ETag: "ffc5e6012614d759283199e67f79071b"<br/>
98
+ Link: &lt;http://localhost:9292/notes/_resolved&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/resolved"<br/>
89
99
  Cache-Control: proxy-revalidate<br/>
90
100
  Content-Type: application/json<br/>
91
- Content-Length: 32<br/>
92
- Connection: keep-alive<br/><br/>
101
+ Content-Length: 32<br/><br/>
93
102
 
94
103
  {"uris":[],"total":0,"offset":0}
95
104
  </div>
96
105
  </p>
97
106
 
98
107
  <p>
99
- Create a note using <a href="http://tools.ietf.org/html/rfc2616#section-9.5">POST</a>:
108
+ Along with the usual metadata, many responses also provide discovery
109
+ information via
110
+ <a href="http://www.mnot.net/drafts/draft-nottingham-http-link-header-03.txt">Link Headers</a>
111
+ as shown above. These links allow user agents to find related resources. The
112
+ purpose of the above rel="resolved" Link Header is to offer a complete representation of
113
+ all documents in a collection as an alternative to the simple list of links provided above.
114
+ We'll look at the the result of using the "resolved" URI after we have created a few resources.
115
+ </p>
116
+
117
+ <p>
118
+ Let's move on, creating a note using <a href="http://tools.ietf.org/html/rfc2616#section-9.5">POST</a>:
100
119
  <div class="code">
101
- $ curl -i -XPOST -d'{"title":"projects"}' http://localhost:3000/notes<br/>
120
+ $ curl -i -XPOST -d'{"title":"projects"}' http://localhost:9292/notes<br/>
102
121
  HTTP/1.1 201 Created<br/>
103
122
  Cache-Control: no-cache<br/>
104
123
  Content-Type: application/json<br/>
105
- Content-Length: 159<br/>
106
- Connection: keep-alive<br/><br/>
124
+ Content-Length: 159<br/><br/>
107
125
 
108
126
  {<br/>
109
127
  "uri":"\/notes\/0dda06f0-b134-012b-a2d8-0017f2c62348",<br/>
@@ -115,35 +133,14 @@ Create a note using <a href="http://tools.ietf.org/html/rfc2616#section-9.5">POS
115
133
  </p>
116
134
 
117
135
  <p>
118
- List the currently available notes again:
119
- <div class="code">
120
- $ curl -i http://localhost:3000/notes<br/>
121
- HTTP/1.1 200 OK<br/>
122
- Last-Modified: Sun, 21 Dec 2008 02:21:52 GMT<br/>
123
- ETag: "dd30142ff023386d2515b41fb88447a5"<br/>
124
- Cache-Control: proxy-revalidate<br/>
125
- Content-Type: application/json<br/>
126
- Content-Length: 79<br/>
127
- Connection: keep-alive<br/><br/>
128
-
129
- {<br/>
130
- "uris":["\/notes\/0dda06f0-b134-012b-a2d8-0017f2c62348"],<br/>
131
- "total":1,<br/>
132
- "offset":0<br/>
133
- }
134
- </div>
135
- </p>
136
-
137
- <p>
138
- Create a note using <a href="http://tools.ietf.org/html/rfc2616#section-9.6">PUT</a>
136
+ Create a different note using <a href="http://tools.ietf.org/html/rfc2616#section-9.6">PUT</a>
139
137
  so that we can specify its location:
140
138
  <div class="code">
141
- $ curl -i -XPUT -d'{"title":"reminders"}' http://localhost:3000/notes/abc<br/>
139
+ $ curl -i -XPUT -d'{"title":"reminders"}' http://localhost:9292/notes/abc<br/>
142
140
  HTTP/1.1 201 Created<br/>
143
141
  Cache-Control: no-cache<br/>
144
142
  Content-Type: application/json<br/>
145
- Content-Length: 126<br/>
146
- Connection: keep-alive<br/><br/>
143
+ Content-Length: 126<br/><br/>
147
144
 
148
145
  {<br/>
149
146
  "uri":"\/notes\/abc",<br/>
@@ -157,37 +154,33 @@ so that we can specify its location:
157
154
  <p>
158
155
  View the new note:
159
156
  <div class="code">
160
- $ curl -i http://localhost:3000/notes/abc<br/>
157
+ $ curl -i http://localhost:9292/notes/abc<br/>
161
158
  HTTP/1.1 200 OK<br/>
162
159
  Last-Modified: Sun, 21 Dec 2008 02:25:19 GMT<br/>
163
160
  ETag: "89487620-b134-012b-a2d8-0017f2c62348"<br/>
164
- Link: &lt;http://localhost:3000/notes/abc/versions&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
161
+ Link: &lt;http://localhost:9292/notes/abc/versions&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
165
162
  Cache-Control: proxy-revalidate<br/>
166
163
  Content-Type: application/json<br/>
167
- Content-Length: 21<br/>
168
- Connection: keep-alive<br/><br/>
164
+ Content-Length: 21<br/><br/>
169
165
 
170
166
  {"title":"reminders"}
171
167
  </div>
172
168
  </p>
173
169
 
174
170
  <p>
175
- Along with the usual metadata, individual resources also provide discovery
176
- information via
177
- <a href="http://www.mnot.net/drafts/draft-nottingham-http-link-header-03.txt">Link Headers</a>
178
- as shown above. These links allow user agents to find related resources such as
179
- the complete history of a document.
171
+ Once again, we see a Link header. This one lists the location of the complete history
172
+ of this particular document. This history contains all versions of the document including
173
+ the most recent. We will see it in action in a moment.
180
174
  </p>
181
175
 
182
176
  <p>
183
- Attempt a careless update and enjoy the failure:
177
+ Next, attempt a careless update of our newest resource and enjoy the failure:
184
178
  <div class="code">
185
- $ curl -i -XPUT -d'{"title":"foo"}' http://localhost:3000/notes/abc<br/>
179
+ $ curl -i -XPUT -d'{"title":"foo"}' http://localhost:9292/notes/abc<br/>
186
180
  HTTP/1.1 400 Bad Request<br/>
187
181
  Cache-Control: no-cache<br/>
188
182
  Content-Type: application/json<br/>
189
- Content-Length: 25<br/>
190
- Connection: keep-alive<br/><br/>
183
+ Content-Length: 25<br/><br/>
191
184
 
192
185
  {"error":"etag required"}
193
186
  </div>
@@ -196,7 +189,7 @@ Attempt a careless update and enjoy the failure:
196
189
  <p>
197
190
  Succeed in updating by being specific:
198
191
  <div class="code">
199
- $ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"foo"}' http://localhost:3000/notes/abc<br/>
192
+ $ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"foo"}' http://localhost:9292/notes/abc<br/>
200
193
  HTTP/1.1 200 OK<br/>
201
194
  Cache-Control: no-cache<br/>
202
195
  Content-Type: application/json<br/>
@@ -221,12 +214,11 @@ created your own "abc" resource.)
221
214
  <p>
222
215
  Watch a secondary, out-of-date client fail at updating by being specific but also being stale:
223
216
  <div class="code">
224
- $ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"foo"}' http://localhost:3000/notes/abc<br/>
217
+ $ curl -i -XPUT -H'If-Match:89487620-b134-012b-a2d8-0017f2c62348' -d'{"title":"bar"}' http://localhost:9292/notes/abc<br/>
225
218
  HTTP/1.1 412 Precondition Failed<br/>
226
219
  Cache-Control: no-cache<br/>
227
220
  Content-Type: application/json<br/>
228
- Content-Length: 31<br/>
229
- Connection: keep-alive<br/><br/>
221
+ Content-Length: 31<br/><br/>
230
222
 
231
223
  {"error":"precondition failed"}
232
224
  </div>
@@ -241,17 +233,17 @@ version of the resource.
241
233
  </p>
242
234
 
243
235
  <p>
244
- Get all versions of the document using the URI tagged with rel="versions" in the
236
+ We can list all versions of the document using the URI tagged with rel="versions" in the
245
237
  link header mentioned above, reverse sorted by Last-Modified, feed style:
246
238
  <div class="code">
247
- $ curl -i http://localhost:3000/notes/abc/versions<br/>
239
+ $ curl -i http://localhost:9292/notes/abc/versions<br/>
248
240
  HTTP/1.1 200 OK<br/>
249
241
  Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
250
- ETag: "bd9226d0c5f34fd761f6c64a0fe8bcc9"<br/>
242
+ ETag: "28ecf6899a45d3cdd0ad82bad56991d1"<br/>
243
+ Link: &lt;http://localhost:9292/notes/abc/versions/_resolved&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/resolved"<br/>
251
244
  Cache-Control: proxy-revalidate<br/>
252
245
  Content-Type: application/json<br/>
253
- Content-Length: 109<br/>
254
- Connection: keep-alive<br/><br/>
246
+ Content-Length: 109<br/><br/>
255
247
 
256
248
  {<br/>
257
249
  "uris":[<br/>
@@ -265,14 +257,84 @@ link header mentioned above, reverse sorted by Last-Modified, feed style:
265
257
  </p>
266
258
 
267
259
  <p>
268
- Delete the document:
260
+ List all versions again, this time using the "resolved" URI from the Link header.
261
+ This effectively delivers the same information that would be obtained by first listing
262
+ the URIs, then fetching each one of them individually.
263
+ <div class="code">
264
+ $ curl -i http://localhost:9292/notes/abc/versions/_resolved<br/>
265
+ HTTP/1.1 200 OK<br/>
266
+ Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
267
+ ETag: "282819afc09d7735fd6801532c0c7033"<br/>
268
+ Link: &lt;http://localhost:9292/notes/abc/versions&gt;; rel="index"<br/>
269
+ Cache-Control: proxy-revalidate<br/>
270
+ Content-Type: application/json<br/>
271
+ Content-Length: 390<br/><br/>
272
+
273
+ {<br/>
274
+ "documents":[<br/>
275
+ {<br/>
276
+ "etag":"522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
277
+ "last_modified":"Sun, 21 Dec 2008 02:30:56 GMT",<br/>
278
+ "document":"{\"title\":\"foo\"}",<br/>
279
+ "uri":"\/notes\/abc"<br/>
280
+ },<br/>
281
+ {<br/>
282
+ "etag":"89487620-b134-012b-a2d8-0017f2c62348",<br/>
283
+ "last_modified":"Sun, 21 Dec 2008 02:25:19 GMT",<br/>
284
+ "document":"{\"title\":\"reminders\"}",<br/>
285
+ "uri":"\/notes\/abc\/versions\/89487620-b134-012b-a2d8-0017f2c62348"}<br/>
286
+ ],<br/>
287
+ "total":2,<br/>
288
+ "offset":0<br/>
289
+ }
290
+ </div>
291
+ </p>
292
+
293
+ <p>
294
+ Notice the resolved response includes a Link header pointing back to its index.
295
+ </p>
296
+
297
+ <p>
298
+ We can use this same "resolved" technique on the main "notes" listing:
269
299
  <div class="code">
270
- $ curl -i -XDELETE -H'If-Match:522be9f0-b135-012b-a2d8-0017f2c62348' http://localhost:3000/notes/abc<br/>
300
+ $ curl -i http://localhost:9292/notes/_resolved<br/>
301
+ HTTP/1.1 200 OK<br/>
302
+ Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
303
+ ETag: "6628242625a7f71cce838a02deb27912"<br/>
304
+ Link: &lt;http://localhost:9292/notes&gt; rel="index"<br/>
305
+ Cache-Control: proxy-revalidate<br/>
306
+ Content-Type: application/json<br/>
307
+ Content-Length: 374<br/><br/>
308
+
309
+ {<br/>
310
+ "documents":[<br/>
311
+ {<br/>
312
+ "etag":"522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
313
+ "last_modified":"Sun, 21 Dec 2008 02:30:56 GMT",<br/>
314
+ "document":"{\"title\":\"foo\"}",<br/>
315
+ "uri":"\/notes\/abc"<br/>
316
+ },<br/>
317
+ {<br/>
318
+ "etag":"0dda0de0-b134-012b-a2d8-0017f2c62348",<br/>
319
+ "last_modified":"Sun, 21 Dec 2008 02:21:52 GMT",<br/>
320
+ "document":"{\"title\":\"projects\"}",<br/>
321
+ "uri":"\/notes\/0dda06f0-b134-012b-a2d8-0017f2c62348"<br/>
322
+ }<br/>
323
+ ],<br/>
324
+ "total":2,<br/>
325
+ "offset":0<br/>
326
+ }
327
+ </div>
328
+ </p>
329
+
330
+ <p>
331
+ Next, let's delete the our most recent document:
332
+ <div class="code">
333
+ $ curl -i -XDELETE -H'If-Match:522be9f0-b135-012b-a2d8-0017f2c62348' http://localhost:9292/notes/abc<br/>
271
334
  HTTP/1.1 200 OK<br/>
272
335
  Cache-Control: no-cache<br/>
273
336
  Content-Type: application/json<br/>
274
- Content-Length: 174<br/>
275
- Connection: keep-alive<br/><br/>
337
+ Content-Length: 174<br/><br/>
276
338
 
277
339
  {<br/>
278
340
  "uri":"\/notes\/abc\/versions\/522be9f0-b135-012b-a2d8-0017f2c62348",<br/>
@@ -286,13 +348,12 @@ Delete the document:
286
348
  <p>
287
349
  Try to GET it again and notice the helpful <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">410</a>:
288
350
  <div class="code">
289
- $ curl -i http://localhost:3000/notes/abc<br/>
351
+ $ curl -i http://localhost:9292/notes/abc<br/>
290
352
  HTTP/1.1 410 Gone<br/>
291
- Link: &lt;http://localhost:3000/notes/abc/versions&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
353
+ Link: &lt;http://localhost:9292/notes/abc/versions&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/versions"<br/>
292
354
  Cache-Control: no-cache<br/>
293
355
  Content-Type: application/json<br/>
294
- Content-Length: 37<br/>
295
- Connection: keep-alive<br/><br/>
356
+ Content-Length: 37<br/><br/>
296
357
 
297
358
  {"error":"entity previously deleted"}
298
359
  </div>
@@ -301,14 +362,14 @@ Try to GET it again and notice the helpful <a href="http://tools.ietf.org/html/r
301
362
  <p>
302
363
  Notice the history is preserved:
303
364
  <div class="code">
304
- $ curl -i http://localhost:3000/notes/abc/versions<br/>
365
+ $ curl -i http://localhost:9292/notes/abc/versions<br/>
305
366
  HTTP/1.1 200 OK<br/>
306
367
  Last-Modified: Sun, 21 Dec 2008 02:30:56 GMT<br/>
307
368
  ETag: "2308ee33e953c9be41221ff7612e5217"<br/>
369
+ Link: &lt;http://localhost:9292/notes/abc/versions/_resolved&gt;; rel="http://joncrosby.me/cloudkit/1.0/rel/resolved"<br/>
308
370
  Cache-Control: proxy-revalidate<br/>
309
371
  Content-Type: application/json<br/>
310
- Content-Length: 157<br/>
311
- Connection: keep-alive<br/><br/>
372
+ Content-Length: 157<br/><br/>
312
373
 
313
374
  {<br/>
314
375
  "uris":[<br/>